Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rust compilation fails on a trivial input with "undefined symbol: __gxx_personality_v0" #17128

Closed
hoodmane opened this issue Jun 2, 2022 · 24 comments

Comments

@hoodmane
Copy link
Collaborator

hoodmane commented Jun 2, 2022

Reproduction

Make a file main.rs consisting of:

fn main() {}

then run:

$ rustc main.rs --target wasm32-unknown-emscripten

results in:

error: undefined symbol: __gxx_personality_v0 (referenced by top-level compiled C/C++ code)
          warning: Link with `-s LLD_REPORT_UNDEFINED` to get more information on undefined symbols
          warning: To disable errors for undefined symbols use `-s ERROR_ON_UNDEFINED_SYMBOLS=0`
          warning: ___gxx_personality_v0 may need to be added to EXPORTED_FUNCTIONS if it arrives from a system library
          Error: Aborting compilation due to previous errors

My minimized version of the failing link command:

emcc -sEXPORTED_FUNCTIONS=[\"_rust_eh_personality\"] \
/src/.docker_home/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/\
lib/rustlib/wasm32-unknown-emscripten/lib/libpanic_unwind-17507931f2722222.rlib  \
-o main.js

libpanic_unwind consists of a single object file panic_unwind<junk>.o which after disassembling is ~280 lines of wat:

Disassembly of panic_unwind.o
000272 func[16] <_ZN4core3mem4drop17h77313b5f84a3c8baE>:
 local[0] type=i32
 i32.const 0
 i32.const 0
 i32.store 2 0
 local.get 1
 i32.load 2 0
 local.get 0
 call 0 <invoke_vi>
 i32.const 0
 i32.load 2 0
 local.set 2
 i32.const 0
 i32.const 0
 i32.store 2 0
 block
   local.get 2
   i32.const 1
   i32.eq
   br_if 0
   block
     local.get 1
     i32.load 2 4
     local.tee 2
     i32.eqz
     br_if 0
     local.get 0
     local.get 2
     local.get 1
     i32.load 2 8
     call 1 <env.__rust_dealloc>
   end
   return
 end
 call 2 <__cxa_find_matching_catch_2>
 local.set 2
 call 3 <getTempRet0>
 drop
 local.get 0
 local.get 1
 call 17 <_ZN5alloc5alloc8box_free17h0b9b1e04bc852fdcE>
 local.get 2
 call 4 <__resumeException>
 unreachable
 end
0002ee func[17] <_ZN5alloc5alloc8box_free17h0b9b1e04bc852fdcE>:
 local[0] type=i32
 block
   local.get 1
   i32.load 2 4
   local.tee 2
   i32.eqz
   br_if 0
   local.get 0
   local.get 2
   local.get 1
   i32.load 2 8
   call 1 <env.__rust_dealloc>
 end
 end
000310 func[18] <__rust_panic_cleanup>:
 local[0] type=i32
 local.get 1
 i32.load 2 0
 call 5 <env.__cxa_begin_catch>
 local.set 2
 block
   local.get 1
   i32.load8_u 0 4
   i32.eqz
   br_if 0
   local.get 2
   i32.load8_u 0 8
   local.set 1
   local.get 2
   i32.const 1
   i32.store8 0 8
   block
     block
       block
         local.get 1
         br_if 0
         local.get 2
         i32.load 2 0
         local.set 1
         local.get 2
         i32.const 0
         i32.store 2 0
         local.get 1
         br_if 2
         i32.const 0
         i32.const 0
         i32.store 2 0
         i32.const 1
         i32.const 0
         i32.const 43
         i32.const 96
         call 7 <invoke_viii>
         i32.const 0
         i32.load 2 0
         local.set 2
         i32.const 0
         i32.const 0
         i32.store 2 0
         local.get 2
         i32.const 1
         i32.eq
         br_if 1
         unreachable
       end
       unreachable
       unreachable
     end
     call 2 <__cxa_find_matching_catch_2>
     drop
     call 3 <getTempRet0>
     drop
     call 8 <env._ZN4core9panicking15panic_no_unwind17h8f4ab85dd1f16b50E>
     unreachable
   end
   local.get 2
   i32.load 2 4
   local.set 2
   call 9 <env.__cxa_end_catch>
   local.get 0
   local.get 2
   i32.store 2 4
   local.get 0
   local.get 1
   i32.store 2 0
   return
 end
 call 10 <env.__rust_foreign_exception>
 unreachable
 end
0003d8 func[19] <__rust_start_panic>:
 local[0..2] type=i32
 global.get 0 <env.__stack_pointer>
 i32.const 16
 i32.sub
 local.tee 1
 global.set 0 <env.__stack_pointer>
 local.get 1
 i32.const 8
 i32.add
 local.get 0
 i32.load 2 0
 local.get 0
 i32.load 2 4
 i32.load 2 12
 call_indirect 0 0
 local.get 1
 i32.load 2 12
 local.set 0
 local.get 1
 i32.load 2 8
 local.set 2
 block
   i32.const 8
   call 11 <env.__cxa_allocate_exception>
   local.tee 3
   br_if 0
   i32.const 0
   i32.const 0
   i32.store 2 0
   local.get 0
   i32.load 2 0
   local.get 2
   call 0 <invoke_vi>
   i32.const 0
   i32.load 2 0
   local.set 3
   i32.const 0
   i32.const 0
   i32.store 2 0
   block
     local.get 3
     i32.const 1
     i32.eq
     br_if 0
     block
       local.get 0
       i32.load 2 4
       local.tee 3
       i32.eqz
       br_if 0
       local.get 2
       local.get 3
       local.get 0
       i32.load 2 8
       call 1 <env.__rust_dealloc>
     end
     local.get 1
     i32.const 16
     i32.add
     global.set 0 <env.__stack_pointer>
     i32.const 3
     return
   end
   call 2 <__cxa_find_matching_catch_2>
   local.set 1
   call 3 <getTempRet0>
   drop
   local.get 2
   local.get 0
   call 17 <_ZN5alloc5alloc8box_free17h0b9b1e04bc852fdcE>
   local.get 1
   call 4 <__resumeException>
   unreachable
 end
 local.get 3
 i32.const 0
 i32.store8 0 8
 local.get 3
 local.get 0
 i32.store 2 4
 local.get 3
 local.get 2
 i32.store 2 0
 local.get 3
 i32.const 56
 i32.const 2
 call 12 <env.__cxa_throw>
 unreachable
 end
0004d2 func[20] <_ZN12panic_unwind8real_imp17exception_cleanup17h416500fee4aa23e5E>:
 local[0] type=i32
 block
   block
     local.get 0
     i32.load 2 0
     local.tee 1
     i32.eqz
     br_if 0
     local.get 0
     i32.load 2 4
     local.set 0
     i32.const 0
     i32.const 0
     i32.store 2 0
     i32.const 3
     local.get 1
     local.get 0
     call 13 <invoke_vii>
     i32.const 0
     i32.load 2 0
     local.set 0
     i32.const 0
     i32.const 0
     i32.store 2 0
     local.get 0
     i32.const 1
     i32.ne
     br_if 1
     call 2 <__cxa_find_matching_catch_2>
     drop
     call 3 <getTempRet0>
     drop
     call 8 <env._ZN4core9panicking15panic_no_unwind17h8f4ab85dd1f16b50E>
     unreachable
   end
   local.get 0
   return
 end
 call 14 <env.__rust_drop_panic>
 unreachable
 end
000545 func[21] <rust_eh_personality>:
 local.get 0
 local.get 1
 local.get 2
 local.get 3
 local.get 4
 call 15 <env.__gxx_personality_v0>
 end
@sbc100
Copy link
Collaborator

sbc100 commented Jun 2, 2022

Do you know where the source code for libpanic_unwind.o lives or how it was compiled?

@hoodmane
Copy link
Collaborator Author

hoodmane commented Jun 2, 2022

@sbc100
Copy link
Collaborator

sbc100 commented Jun 2, 2022

Emscripten does not currently define that symbol. @aheejin, is this something we should be defining? What does it mean exactly? Or should we suggest that the rust library needs to be updated/fixed instead?

@sbc100
Copy link
Collaborator

sbc100 commented Jun 2, 2022

It looks like that symbol is maybe present when building with wasm exceptions but not with SjLj (emscripten) exceptions. I removed the dummy version for this reason back in #12820

@hoodmane
Copy link
Collaborator Author

hoodmane commented Jun 2, 2022

I get exactly the same linking error even with -fwasm-exceptions or with -fexceptions

@hoodmane
Copy link
Collaborator Author

hoodmane commented Jun 3, 2022

Actually that is not true, I get a different error with -fwasm-exceptions:

$ rustc main.rs --target wasm32-unknown-emscripten -C link-args="-fwasm-exceptions -s DISABLE_EXCEPTION_THROWING=0"
wasm-ld: error: symbol exported via --export not found: __cxa_is_pointer_type
wasm-ld: error: symbol exported via --export not found: __cxa_can_catch

@hoodmane
Copy link
Collaborator Author

hoodmane commented Jun 3, 2022

Well it does seem like just declaring __gxx_personality_v0 in src/library.js fixes the problem. I can't find evidence that it is ever invoked, even if I panic.

hoodmane added a commit to hoodmane/pyo3 that referenced this issue Jun 6, 2022
This adds CI to build libpython3.11 for wasm32-emscripten and
running tests against it. We need to patch instant to work
around the emscripten_get_now:
sebcrozet/instant#47

We also have to patch emscripten to work aroung the "undefined
symbol gxx_personality_v0" error:
emscripten-core/emscripten#17128

I set up a nox file to download and install emscripten,
download and build cpython, set appropriate environment variables
then run cargo test. The workflow just installs python, rust,
node, and nox and runs the nox session.

I xfailed all the test failures. There are problems with datetime.
iter_dict_nosegv and test_filenotfounderror should probably be
fixable. The tests that involve threads or asyncio probably can't
be fixed.
hoodmane added a commit to hoodmane/pyo3 that referenced this issue Jun 6, 2022
This adds CI to build libpython3.11 for wasm32-emscripten and
running tests against it. We need to patch instant to work
around the emscripten_get_now:
sebcrozet/instant#47

We also have to patch emscripten to work aroung the "undefined
symbol gxx_personality_v0" error:
emscripten-core/emscripten#17128

I set up a nox file to download and install emscripten,
download and build cpython, set appropriate environment variables
then run cargo test. The workflow just installs python, rust,
node, and nox and runs the nox session.

I xfailed all the test failures. There are problems with datetime.
iter_dict_nosegv and test_filenotfounderror should probably be
fixable. The tests that involve threads or asyncio probably can't
be fixed.
@sbc100
Copy link
Collaborator

sbc100 commented Jun 6, 2022

Well it does seem like just declaring __gxx_personality_v0 in src/library.js fixes the problem. I can't find evidence that it is ever invoked, even if I panic.

You could define it anywhere you like.. but the real fix is most likely to remove the reference to it in the rust source code.

@hoodmane
Copy link
Collaborator Author

hoodmane commented Jun 6, 2022

Yeah. Patching emscripten is so convenient that it is often the path of least resistance to fixing problems caused elsewhere.

@hoodmane
Copy link
Collaborator Author

hoodmane commented Jun 6, 2022

@brson @Amanieu

@brson
Copy link
Contributor

brson commented Jun 6, 2022

Per rust-lang/rust#85821 it looks like this should be fixed in Rust, though it is unfortunate the old unwinding personality symbol disappeared. I cannot help though.

@Amanieu
Copy link

Amanieu commented Jun 6, 2022

I'm not too familiar with emscripten, but if the function doesn't do anything then we can replace it with a stub in Rust. We do the same for MSVC exceptions here: https://github.com/rust-lang/rust/blob/9d20fd109809f20c049d6895a5be27a1fbd39daa/library/panic_unwind/src/seh.rs#L327

@hoodmane
Copy link
Collaborator Author

hoodmane commented Jun 6, 2022

I think the function is never called. I stubbed it with a function that logs the input and ran various panic tests and it never got invoked. Though for instance catch_unwind doesn't work (at least the example in the docs doesn't work). The panic isn't caught.

hoodmane added a commit to hoodmane/pyo3 that referenced this issue Jun 6, 2022
This adds CI to build libpython3.11 for wasm32-emscripten and
running tests against it. We need to patch instant to work
around the emscripten_get_now:
sebcrozet/instant#47

We also have to patch emscripten to work aroung the "undefined
symbol gxx_personality_v0" error:
emscripten-core/emscripten#17128

I set up a nox file to download and install emscripten,
download and build cpython, set appropriate environment variables
then run cargo test. The workflow just installs python, rust,
node, and nox and runs the nox session.

I xfailed all the test failures. There are problems with datetime.
iter_dict_nosegv and test_filenotfounderror should probably be
fixable. The tests that involve threads or asyncio probably can't
be fixed.
@aheejin
Copy link
Member

aheejin commented Jun 7, 2022

I don't know much about Rust. Why does Rust need that symbol? It is a name of the personality function in libc++abi. I don't think Rust would use libc++abi internally?

Also I'm not very familiar with the status of EH support in Rust. Does it use Emscripten EH or Wasm EH or something else? In case of Wasm EH I think you would need a frontend support that can generate the specific EH IR our LLVM backend expects. Clang has that support but I'm not sure if Rust has it too?

By the way neither of Wasm EH and Emscripten EH has that symbol. Emscripten EH doesn't use a personality function at all, and it instead use __cxa_find_matching_catch JS function:

// Finds a suitable catch clause for when an exception is thrown.
// In normal compilers, this functionality is handled by the C++
// 'personality' routine. This is passed a fairly complex structure
// relating to the context of the exception and makes judgements
// about how to handle it. Some of it is about matching a suitable
// catch clause, and some of it is about unwinding. We already handle
// unwinding using 'if' blocks around each function, so the remaining
// functionality boils down to picking a suitable 'catch' block.
// We'll do that here, instead, to keep things simpler.
__cxa_find_matching_catch__deps: ['$exceptionLast', '$ExceptionInfo', '__resumeException', '__cxa_can_catch'],
//__cxa_find_matching_catch__sig: 'p',
__cxa_find_matching_catch: function() {
var thrown = exceptionLast;
if (!thrown) {
// just pass through the null ptr
setTempRet0(0);
return 0;
}
var info = new ExceptionInfo(thrown);
info.set_adjusted_ptr(thrown);
var thrownType = info.get_type();
if (!thrownType) {
// just pass through the thrown ptr
setTempRet0(0);
return thrown;
}
var typeArray = Array.prototype.slice.call(arguments);
// can_catch receives a **, add indirection
#if EXCEPTION_DEBUG
err("__cxa_find_matching_catch on " + ptrToString(thrown));
#endif
// The different catch blocks are denoted by different types.
// Due to inheritance, those types may not precisely match the
// type of the thrown object. Find one which matches, and
// return the type of the catch block which should be called.
for (var i = 0; i < typeArray.length; i++) {
var caughtType = typeArray[i];
if (caughtType === 0 || caughtType === thrownType) {
// Catch all clause matched or exactly the same type is caught
break;
}
var adjusted_ptr_addr = info.ptr + {{{ C_STRUCTS.__cxa_exception.adjustedPtr }}};
if ({{{ exportedAsmFunc('___cxa_can_catch') }}}(caughtType, thrownType, adjusted_ptr_addr)) {
#if EXCEPTION_DEBUG
err(" __cxa_find_matching_catch found " + [ptrToString(info.get_adjusted_ptr()), caughtType]);
#endif
setTempRet0(caughtType);
return thrown;
}
}
setTempRet0(thrownType);
return thrown;
},

Wasm EH uses a personality function, but we use a different name __gxx_personality_wasm0:

#ifdef __USING_WASM_EXCEPTIONS__
_Unwind_Reason_Code __gxx_personality_wasm0

@Amanieu
Copy link

Amanieu commented Jun 7, 2022

The personality function is passed to LLVM in the personality attribute when a function is defined. We're basically following the behavior of Clang here, which uses __gxx_personality_v0 on emscripten: https://godbolt.org/z/bWr6bv79x

@sbc100
Copy link
Collaborator

sbc100 commented Jun 7, 2022

Interesting.. I see that symbol in the bitcode but then i gets compiled away at some point:

https://godbolt.org/z/WaaaYrfrG

I wonder why the rustc usage doesn't also get compiled away?

@sbc100
Copy link
Collaborator

sbc100 commented Jun 7, 2022

Same with emscripten exceptions.. that symbol gets compiled away: https://godbolt.org/z/heasWYdn9

It is interesting that in both cases clang is generating reference to __gxx_wasm_personality_v0 even though with wasm exceptions we apparently use a different personality and with emscripten exceptions we don't define a personality at all.

@Amanieu
Copy link

Amanieu commented Jun 7, 2022

The difference is that Rust wraps __gxx_personality_v0 in another function (rust_eh_personality), which is not eliminated by LLVM and causes this undefined symbol error.

@sbc100
Copy link
Collaborator

sbc100 commented Jun 7, 2022

Actually it looks like -fwasm-exceptions causes __gxx_wasm_personality_v0 to be generated by clang. Which seems like it should be in sync with

#ifdef __USING_WASM_EXCEPTIONS__
_Unwind_Reason_Code __gxx_personality_wasm0
.

It looks like clang chooses which one to use here: https://github.com/llvm/llvm-project/blob/907aedbb3d08a28f00a6a45b0ec82cdc373115c9/clang/lib/CodeGen/CGException.cpp#L173-L189

davidhewitt pushed a commit to PyO3/pyo3 that referenced this issue Jun 8, 2022
* ci: test on emscripten target

This adds CI to build libpython3.11 for wasm32-emscripten and
running tests against it. We need to patch instant to work
around the emscripten_get_now:
sebcrozet/instant#47

We also have to patch emscripten to work aroung the "undefined
symbol gxx_personality_v0" error:
emscripten-core/emscripten#17128

I set up a nox file to download and install emscripten,
download and build cpython, set appropriate environment variables
then run cargo test. The workflow just installs python, rust,
node, and nox and runs the nox session.

I xfailed all the test failures. There are problems with datetime.
iter_dict_nosegv and test_filenotfounderror should probably be
fixable. The tests that involve threads or asyncio probably can't
be fixed.

* Some cleanup

* Remove instant patch

* Add explanations for xfails
@aheejin
Copy link
Member

aheejin commented Jun 8, 2022

The difference is that Rust wraps __gxx_personality_v0 in another function (rust_eh_personality), which is not eliminated by LLVM and causes this undefined symbol error.

I ran rustc myself, and it looks the current rustc is using Emscripten EH, in which case it doesn't use any personality functions at all. I'm not sure what rust_eh_personality does, but I don't think __gxx_personality_v0 should ever run during program execution when using Emscripten EH. (It's true for Wasm EH too, because we use __gxx_wasm_personality_v0)

I'm not sure rust_eh_personality does, but can we remove it or the call to __gxx_personality_v0 inside?

@aheejin
Copy link
Member

aheejin commented Jun 8, 2022

By the way if you can't compile that empty main.rs, how are people using Rust with Wasm? Are people using wasm32-unknown-emscripten?

I tried wasm32-unknown-unknown instead of wasm32-unknown-emscripten, and that compiles. What's the difference?

@hoodmane
Copy link
Collaborator Author

hoodmane commented Jun 8, 2022

I don't think __gxx_personality_v0 should ever run during program execution when using Emscripten EH. (It's true for Wasm EH too, because we use __gxx_wasm_personality_v0)

This seems correct in practice. As I mentioned further up the thread, I patched in a version of __gxx_personality_v0 that logs its output to the console and then ran a bunch of panic tests and it was never called.

I'm not sure rust_eh_personality does, but can we remove it or the call to __gxx_personality_v0 inside?

@Amanieu suggested this further up in the thread. I think this is the clear consensus from this discussion, I will open a PR on Rust.

if you can't compile that empty main.rs, how are people using Rust with Wasm? Are people using wasm32-unknown-emscripten?

People are using the wasm32-unknown-unknown target.

I tried wasm32-unknown-unknown instead of wasm32-unknown-emscripten, and that compiles. What's the difference?

wasm32-unknown-emscripten links with emscripten, whereas wasm32-unknown-unknown uses clang/llvm directly to link.

hoodmane added a commit to hoodmane/rust that referenced this issue Jun 8, 2022
This resolves rust-lang#85821. See also the discussion here:
emscripten-core/emscripten#17128

The consensus seems to be that rust_eh_personality is never invoked.
I patched __gxx_personality_v0 to log invocations and then ran
various panic tests and it was never called, so this analysis matches
what seems to happen in practice. This replaces the definition with
an abort, modeled on the structured exception handling implementation.
JohnTitor added a commit to JohnTitor/rust that referenced this issue Jun 9, 2022
… r=Amanieu

Don't use __gxx_personality_v0 in panic_unwind on emscripten target

This resolves rust-lang#85821. See also the discussion here:
emscripten-core/emscripten#17128

The consensus seems to be that rust_eh_personality is never invoked.
I patched __gxx_personality_v0 to log invocations and then ran
various panic tests and it was never called, so this analysis matches
what seems to happen in practice. This replaces the definition with
an abort, modeled on the structured exception handling implementation.
JohnTitor added a commit to JohnTitor/rust that referenced this issue Jun 10, 2022
… r=Amanieu

Don't use __gxx_personality_v0 in panic_unwind on emscripten target

This resolves rust-lang#85821. See also the discussion here:
emscripten-core/emscripten#17128

The consensus seems to be that rust_eh_personality is never invoked.
I patched __gxx_personality_v0 to log invocations and then ran
various panic tests and it was never called, so this analysis matches
what seems to happen in practice. This replaces the definition with
an abort, modeled on the structured exception handling implementation.
@hoodmane
Copy link
Collaborator Author

Thanks everyone!

workingjubilee pushed a commit to tcdi/postgrestd that referenced this issue Sep 15, 2022
This resolves #85821. See also the discussion here:
emscripten-core/emscripten#17128

The consensus seems to be that rust_eh_personality is never invoked.
I patched __gxx_personality_v0 to log invocations and then ran
various panic tests and it was never called, so this analysis matches
what seems to happen in practice. This replaces the definition with
an abort, modeled on the structured exception handling implementation.
workingjubilee pushed a commit to tcdi/postgrestd that referenced this issue Sep 15, 2022
Don't use __gxx_personality_v0 in panic_unwind on emscripten target

This resolves #85821. See also the discussion here:
emscripten-core/emscripten#17128

The consensus seems to be that rust_eh_personality is never invoked.
I patched __gxx_personality_v0 to log invocations and then ran
various panic tests and it was never called, so this analysis matches
what seems to happen in practice. This replaces the definition with
an abort, modeled on the structured exception handling implementation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants