emscripten-core/emscripten

Using thread_local results in runtime linker error when using multiple SIDE_MODULES

Closed this issue · 2 comments

Please include the following in your bug report:

Version of emscripten/emsdk:

emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 4.0.10-git
clang version 20.1.0 (https://github.com/llvm/llvm-project 24a30daaa559829ad079f2ff7f73eb4e18095f88)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /Users/jechter/Downloads/LLVM-20.1.0-macOS-ARM64/bin

Description

When using thread_local variables for structs without a constructor, emcc may generate wasm like this for the thread-local initialization routine:

  (@dylink.0
    (mem-info (memory 20 2))
    (needed "libb.wasm")
    (import-info "env" "_ZTH7g_TLS_a" binding-weak undefined)
  )

The runtime dynamic loader code detects this scenario, and can ignore weak symbols like this, using this JS code:

        if (!currentModuleWeakSymbols.has(symName)) {
          // Any non-weak reference to a symbol marks it as `required`, which
          // enabled `reportUndefinedSymbols` to report undefined symbol errors
          // correctly.
          rtn.required = true;
        }

However, this assumes a single currentModuleWeakSymbols variable. If the module being loaded loads another module itself, currentModuleWeakSymbols is overwritten before it tries to load the symbol. This results in a runtime error like:

Aborted(Assertion failed: undefined symbol '_ZTH7g_TLS_a'. perhaps a side module was not linked in? if this global was expected to arrive from a system library, try to build the MAIN_MODULE with EMCC_FORCE_STDLIBS=1 in the environment)

I believe that instead of currentModuleWeakSymbols, we need to have a Set of weak symbols per module for this to work correctly.

Reproduction steps

Set up files like this:

main.cpp

void LibAFunc();

int main()
{
    LibAFunc();
}

liba.cpp

#include <stdio.h>

struct TLSA 
{
    int val1, val2;
};

extern thread_local TLSA g_TLS_a;

void LibBFunc();

void LibAFunc()
{
    LibBFunc();
    printf("g_TLS_a %d\n", g_TLS_a.val1);
}

libatls.cpp

struct TLSA 
{
    int val1, val2;
};

thread_local TLSA g_TLS_a;

libb.cpp

#include <stdio.h>

void LibBFunc()
{
    printf("Hello from b");
}

Build like this:

emcc libb.cpp -sSIDE_MODULE -o libb.wasm
emcc liba.cpp libatls.cpp -sSIDE_MODULE libb.wasm  -o liba.wasm
emcc main.cpp liba.wasm -L. -sMAIN_MODULE -o main.html

The resulting html will show the problem.

Yes, I believe the code is assuming that only one module can be loaded at a time. If that is not true then this is certainly a bug.

Yeah, in this example, loading one module (liba.wasm) requires loading another dependent module (libb.wasm), which seems to break this code.