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

WebAssembly and Exception Handling (throw) #23442

Open
anutosh491 opened this issue Jan 17, 2025 · 6 comments
Open

WebAssembly and Exception Handling (throw) #23442

anutosh491 opened this issue Jan 17, 2025 · 6 comments

Comments

@anutosh491
Copy link

Hey all,

After getting clang-repl running in the browser, I worked on integration it with jupyterlite. Xeus-cpp, a C++ Jupyter kernel provides a way to integrate it. Here is a static link that can be used to try C++ completely in the browser (https://compiler-research.org/xeus-cpp/lab/index.html) . An example notebook xeus-cpp-lite-demo.ipynb has been provided to show what all can be acheived.

Coming back to the issue. I see we can't run throw (or a try catch blocking involving throw) while running clang-repl in the browser.

Image

The debug logs tell me that this comes from dlopen

Image

All this can be tried through the static link above.

@anutosh491
Copy link
Author

anutosh491 commented Jan 17, 2025

Now the point is that for running clang-repl in the browser. This is the workflow taken

code -> PTU -> LLVM IR -> wasm object -> wasm binary -> loaded on top of main module using dlopen

  1. So as it fails in the dlopen step, we know for sure that the LLVM IR is being produced and also a wasm binary is being produced (hopefully correctly)

Pasting them down below just for reference

i) LLVM IR (only relevant part)

@_ZTIi = external constant ptr
@llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I_incr_module_2, ptr null }]
define internal void @__stmts__0() #0 {
  %1 = call ptr @__cxa_allocate_exception(i32 4) #2
  store i32 1, ptr %1, align 16
  call void @__cxa_throw(ptr %1, ptr @_ZTIi, ptr null) #3
  unreachable
}
declare ptr @__cxa_allocate_exception(i32) #0
declare void @__cxa_throw(ptr, ptr, ptr) #0
; Function Attrs: noinline
define internal void @_GLOBAL__sub_I_incr_module_2() #1 {
  call void @__stmts__0()
  ret void
}

ii) wasm module

(module $incr_module_2.wasm
  (memory $env.memory (;0;) (import "env" "memory") 0)
  (table $env.__indirect_function_table (;0;) (import "env" "__indirect_function_table") 0 funcref)
  (global $__memory_base (;0;) (import "env" "__memory_base") i32)
  (global $__table_base (;1;) (import "env" "__table_base") i32)
  (func $__cxa_allocate_exception (;0;) (import "env" "__cxa_allocate_exception") (param i32) (result i32))
  (func $__cxa_throw (;1;) (import "env" "__cxa_throw") (param i32 i32 i32))
  (global $typeinfo for int (;2;) (import "GOT.mem" "_ZTIi") (mut i32))
  (func $__wasm_call_ctors (;2;) (export "__wasm_call_ctors")
    call $_GLOBAL__sub_I_incr_module_2
  )
  (func $__wasm_apply_data_relocs (;3;) (export "__wasm_apply_data_relocs")
  )
  (func $__stmts__0 (;4;)
    (local $var0 i32)
    i32.const 4
    call $__cxa_allocate_exception
    local.tee $var0
    i32.const 1
    i32.store
    local.get $var0
    global.get $typeinfo for int
    i32.const 0
    call $__cxa_throw
    unreachable
  )
  (func $_GLOBAL__sub_I_incr_module_2 (;5;)
    call $__stmts__0
  )
)

I think this looks correct to me !

@anutosh491
Copy link
Author

Now coming back to the dloepn step. The debugger through chrome tools tells me that this is the last part where it ends up

var init = moduleExports['__wasm_call_ctors'];
if (init) {
if (runtimeInitialized) {
init();

Which means it is trying to execute this block I'd guess

  (func $__wasm_call_ctors (;2;) (export "__wasm_call_ctors")
    call $_GLOBAL__sub_I_incr_module_2
  )
  (func $__stmts__0 (;4;)
    (local $var0 i32)
    i32.const 4
    call $__cxa_allocate_exception
    local.tee $var0
    i32.const 1
    i32.store
    local.get $var0
    global.get $typeinfo for int
    i32.const 0
    call $__cxa_throw
    unreachable
  )
  (func $_GLOBAL__sub_I_incr_module_2 (;5;)
    call $__stmts__0
  )

But it isn't able to. Now __wasm_call_ctors calls _GLOBAL__sub_I_incr_module_5 which simply calls __stmts__0 ... So I am guessing its just not able to run __stmts__0 but I think even that is being framed correctly ?

@anutosh491
Copy link
Author

anutosh491 commented Jan 17, 2025

cc @sbc100 @kripken

Here's what I thought might be going wrong.

  1. Just as a sanity check I thought that I should confirm the presence of symbols in the final xcpp.wasm be built (the wasm binary out of xeus-cpp that acts as a main module)
(xeus-lite-host) anutosh491@Anutoshs-MacBook-Air build % wasm-objdump -x xcpp.wasm | grep __cxa
 - func[1] sig=10 <__cxa_find_matching_catch_2> <- env.__cxa_find_matching_catch_2
.....
 - func[9678] <__cxa_allocate_exception>
.....
 - global[3] <__cxa_throw>

(xeus-lite-host) anutosh491@Anutoshs-MacBook-Air build % wasm-objdump -x xcpp.wasm | grep _ZTIi
 - global[1701] i32 mutable=0 <_ZTIi> - init i32=409276
 - global[1701] -> "_ZTIi"

I think we have everything

  1. I thought this might be a -fwasm-exceptions or -fexceptions thingy. I realized we build xeus-cpp with -fexceptions but llvm isn't using that (we obviously need to build llvm for wasm to get libclangInterpreter.a which facilitates using clang-repl in the web). So I tried this too but didn't help me in any way. Still get the same result.

If y'all are interested in the configuration, this is what i used.

emcmake cmake -DCMAKE_BUILD_TYPE=MinSizeRel         \
    -DBUILD_SHARED_LIBS=OFF                         \
    -DLLVM_HOST_TRIPLE=wasm32-unknown-emscripten    \
    -DLLVM_TARGETS_TO_BUILD="WebAssembly"           \
    -DLLVM_INCLUDE_BENCHMARKS=OFF                   \
    -DLLVM_INCLUDE_EXAMPLES=OFF                     \
    -DLLVM_INCLUDE_TESTS=OFF                        \
    -DLLVM_ENABLE_LIBEDIT=OFF                       \
    -DLLVM_ENABLE_PROJECTS="clang;lld"              \
    -DLLVM_ENABLE_THREADS=OFF                       \
    -DCLANG_ENABLE_STATIC_ANALYZER=OFF              \
    -DCLANG_ENABLE_ARCMT=OFF                        \
    -DCLANG_ENABLE_BOOTSTRAP=OFF                    \
    -DLLVM_ENABLE_ZSTD=OFF                          \
    -DLLVM_ENABLE_LIBXML2=OFF                       \
    -DCMAKE_CXX_FLAGS="-Dwait4=__syscall_wait4 -fexceptions" \
    ../llvm

Apart from adding the -fexceptions flag here ... everything is what we already use for getting the static link to work !

@sbc100
Copy link
Collaborator

sbc100 commented Jan 17, 2025

Is there still an issue here?

You are correct that you need to make sure that -fwasm-exceptions is either used everywhere, or nowhere. You cannot mix code compiled with and without that flag. Did making that consistent fix your issue?

From your original backtrace looks like the code its trying to load a DLL called "const char*", which is very odd. Can you stop in the debugger and see why that might be? Is the name of file being loaded really "const char*"?

(BTW, you file bugs like this it would be very helpful if you could copy and paste the text rather than attaching screenshots. Using test make it much easier for use to search / copy / etc within the issue.)

@anutosh491
Copy link
Author

anutosh491 commented Jan 20, 2025

Is there still an issue here?

Yes it is.

Did making that consistent fix your issue?

So I think we tried building the whole stack with -fexceptions (jupyterlite, xeus-cpp, llvm etc) and we haven't moved to -fwasm-exceptions yet as we thought using one of these for the whole toolchain would be enough !

Can you stop in the debugger and see why that might be? Is the name of file being loaded really "const char*"?

Is it ? So when we use clang-repl in the browser ever code block produced a file named incr_module_xx.wasm where xx is the code block number. So yeah don't think that's the file name here !

I think it, it might just be the exception ptr type or something (not sure). The below issue looks relevant here.
#6330

EDIT: Also just questioning my breakdown here. The wasm module generated looks correct to me and I think it is the init() call that I referred above that doesn't work ! Maybe someone could confirm that for me ?

@sbc100
Copy link
Collaborator

sbc100 commented Jan 20, 2025

I think that fact that _dlopen_js is being called with the string "const char*" rather than the name of a DLL is really the clue. That looks really wrong.

Can you break at that callsite and see the string ptr value being passed to _dlopen_js? Presumably the user code passed a completely different string.. can you print the ptr value on C++ side too? It looks like dlopen is being called from side module with function names like $func917. I imagine somehow the DLL is confused about where its static data lives? Perhaps __memory_base was not correctly set when the DLL was loaded?

Can you try building you side modules with --profiling-funcs so you get useful functions names instead of $func917?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants