Skip to content

Using thread_local results in runtime linker error when using multiple SIDE_MODULES #25214

@jechter

Description

@jechter

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions