emscripten-core / emscripten

Emscripten: An LLVM-to-WebAssembly Compiler
Other
25.73k stars 3.3k forks source link

undefined symbol: __cxa_find_matching_catch_2 when mixing wasm and native exception handling #20534

Open juj opened 11 months ago

juj commented 11 months ago

a.cpp

#include <stdexcept>
#include <emscripten.h>

void foo()
{
    try
    {
        throw std::runtime_error("asdf");
    } catch(const std::exception &e)
    {
    }
}
em++ a.cpp -c -o a.o -sDISABLE_EXCEPTION_CATCHING=0
emar rc a.a a.o

main.cpp

void foo();

int main()
{
    foo();
}
em++ main.cpp a.a -o a.js -fwasm-exceptions

produces

C:\emsdk\emscripten\main>em++ main.cpp a.a -o a.js -fwasm-exceptions
cache:INFO: generating system asset: symbol_lists/f6c1c2a3db858f2ea2bded40e96b6bc4eefb0515.json... (this will be cached in "C:\emsdk\emscripten\main\cache\symbol_lists\f6c1c2a3db858f2ea2bded40e96b6bc4eefb0515.json" for subsequent builds)
cache:INFO:  - ok
emscripten:ERROR: undefined symbol: __cxa_find_matching_catch_3 (referenced by top-level compiled C/C++ code)
emscripten:ERROR: undefined symbol: __resumeException (referenced by top-level compiled C/C++ code)
emscripten:ERROR: undefined symbol: llvm_eh_typeid_for (referenced by top-level compiled C/C++ code)

It is unclear if wasm and native exception handling could be mixed (probably not?), though the above error message does not immediately make it clear to the user why they are getting that error. (imagine a large project, where one or more of hundreds of built files missed the -fwasm-exceptions compile flag)

juj commented 11 months ago

When building with Wasm exception handling -fwasm-exceptions, should any compiled code that is using setjmp/longjmp ever emit a dependency to emscripten_longjmp, or is emscripten_longjmp strictly a Emscripten/JS exceptions mode feature?

I am seeing a .o file that I built with -fwasm-exceptions to have U emscripten_longjmp dependency, and when generating the final .html file, that is erroring as undefined, and I am wondering where things are going wrong here.

juj commented 11 months ago

Answering myself, I believe that according to emscripten\main\system\lib\compiler-rt\emscripten_setjmp.c, there's

#if !defined(__USING_WASM_SJLJ__)

#include "emscripten_internal.h"

void emscripten_longjmp(uintptr_t env, int val) {
  setThrew(env, val);
  _emscripten_throw_longjmp();
}
#endif

suggesting that emscripten_longjmp is supposed to be strictly an Emscripten EH thing, and not present when building with -fwasm-exceptions?

Still for some odd reason I am getting emscripten_longjmp dependency into the build even when I have -fwasm-exceptions (at both compile and link time... very odd)

juj commented 11 months ago

Now I see the issue.

Passing emcc code_using_longjmp.c -c -o a.o -fwasm-exceptions -fno-exceptions will result in emitting Emscripten longjmp instead of Wasm longjmp, and then as result the link with emcc a.o -o a.js -fwasm-exceptions will fail with emscripten_longjmp being missing.

Are -fwasm-exceptions and -fno-exceptions intended to be mutually exclusive flags?

kripken commented 11 months ago

Are -fwasm-exceptions and -fno-exceptions intended to be mutually exclusive flags?

That surprises me, but I'm not sure. @aheejin ?

aheejin commented 11 months ago

Sorry for the delayed reply.

It is unclear if wasm and native exception handling could be mixed (probably not?),

I don't think they can.

though the above error message does not immediately make it clear to the user why they are getting that error. (imagine a large project, where one or more of hundreds of built files missed the -fwasm-exceptions compile flag)

We error out when two conflicting flags are given at the same time: https://github.com/emscripten-core/emscripten/blob/ac8d9f0a53b2e6a3fdb7db92371fd702c0598613/emcc.py#L1683-L1684 But yeah, we don't error out in the cases like you described, where each object file is compiled with Emscripten EH and then linked with Wasm EH. To give a helpful error message on this kind of case I guess we need a separate phase to check whether each undefined symbol belongs to the generated symbols with one of the EH schemes and then if it violates the current EH flag given to the linker... which doesn't sound simple.

When building with Wasm exception handling -fwasm-exceptions, should any compiled code that is using setjmp/longjmp ever emit a dependency to emscripten_longjmp, or is emscripten_longjmp strictly a Emscripten/JS exceptions mode feature?

It is strictly a Emscripten/JS mode feature.

I am seeing a .o file that I built with -fwasm-exceptions to have U emscripten_longjmp dependency, and when generating the final .html file, that is erroring as undefined, and I am wondering where things are going wrong here.

I can think of one possibility; it might be using Wasm EH with Emscripten SjLj. Now we default SjLj mode to that of EH mode, so if you just use -fwasm-exceptions and nothing else, you get to use Wasm EH & Wasm SjLj. But that was only after #18692, after Feb 2023, so object files built before that with -fwasm-exceptions could use Wasm EH with Emscripten SjLj. Even after Feb 2023, if you explicitly specify the combination using -fwasm-exceptions -sSUPPORT_LONGJMP=emscripten, you can be using these two at the same time.


I'm writing as I'm reading your comments in order, and just saw the -fno-exceptions thing. Will continue on that after investigating.

aheejin commented 11 months ago

Passing emcc code_using_longjmp.c -c -o a.o -fwasm-exceptions -fno-exceptions will result in emitting Emscripten longjmp instead of Wasm longjmp, and then as result the link with emcc a.o -o a.js -fwasm-exceptions will fail with emscripten_longjmp being missing.

Are -fwasm-exceptions and -fno-exceptions intended to be mutually exclusive flags?

I think what's happening here is emcc just passes these flags verbatim to clang, and in clang the last flag takes effect when conflicting flags are given. So -fwasm-exceptions -fno-exceptions is equivalent to -fno-exceptions, and -fno-exceptions -fwasm-exceptions is equivalent to -fwasm-exceptions. And with -fno-exceptions, with no EH mode to defualt to, Emscripten by default chooses Emscripten EH. That's why emscripten_longjmp ends up there.

Not sure whether we should error out -fwasm-exceptions -fno-exceptions or vice versa. Clang's convention for this situation is to use the last one, and I think it is good if emcc has the same convention.

sbc100 commented 11 months ago

Not sure whether we should error out -fwasm-exceptions -fno-exceptions or vice versa. Clang's convention for this situation is to use the last one, and I think it is good if emcc has the same convention.

That actually seems like it might be useful to specify those two options together. For example, if I want to use wasm-exceptions for SJLJ but I want to fully disable C++ exception handling.

aheejin commented 11 months ago

Not sure whether we should error out -fwasm-exceptions -fno-exceptions or vice versa. Clang's convention for this situation is to use the last one, and I think it is good if emcc has the same convention.

That actually seems like it might be useful to specify those two options together. For example, if I want to use wasm-exceptions for SJLJ but I want to fully disable C++ exception handling.

But it doesn't work that way currently. Clang just takes the last one if the two flags are given together. (The same with -fexceptions -fno-exceptions. -fwasm-exceptions are just one of those family) If we give the combination another meaning that can be confusing because the behavior would be different from that of clang.

sbc100 commented 11 months ago

So how you currently disable C++ exceptions while opting into wasm exceptions for SJLJ? If thats not possible today we should find a way to allow it.

aheejin commented 11 months ago

You can use -sSUPPORT_LONGJMP=wasm to do that.