Skip to content

O3 optimization and -sMAIN_MODULE=2 makes dynamic linking impossible #25952

@pkhead

Description

@pkhead

I find that when using O3 optimization, this global necessary for dynamic linking to work is eliminated. I can not find a way to keep this export. I tried passing __stack_pointer to EXPORTED_FUNCTIONS and EXPORTED_RUNTIME_METHODS, both of which create an error as it does not recognize that symbol. I also tried passing -Wl,--export=__stack_pointer or -Wl,--export,__stack_pointer to emcc, both of which do nothing. Even then, I feel like this is unintended behavior.

I presume that CMake uses O3 optimization by default when compiling release builds; this is how I found the issue. Perhaps there is a way to change that, but I would prefer for me to be able to use dlopen even with O3 optimization.

emcc version

> emcc -v
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 4.0.19 (08e2de1031913e4ba7963b1c56f35f036a7d4d56)
clang version 22.0.0git (https:/github.com/llvm/llvm-project 12f392cff10fcc70b4ec4f01ab386922742e9136)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: C:\dev\emsdk\upstream\bin

emcc compile command

> emcc -o out/main.js -O3 -sMAIN_MODULE=2 "-Wl,-lnodefs.js" main.c -v
 "C:/dev/emsdk/upstream/bin\clang.exe" -target wasm32-unknown-emscripten -fignore-exceptions -fPIC -fvisibility=default -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr '--sysroot=C:\dev\emsdk\upstream\emscripten\cache\sysroot' -DEMSCRIPTEN -Xclang '-iwithsysroot/include\fakesdl' -Xclang '-iwithsysroot/include\compat' -O3 -v -c main.c -o 'C:\Users\nabca\AppData\Local\Temp\emscripten_temp_fnr2y61r\main.o'
clang version 22.0.0git (https:/github.com/llvm/llvm-project 12f392cff10fcc70b4ec4f01ab386922742e9136)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: C:\dev\emsdk\upstream\bin
 (in-process)
 "C:\\dev\\emsdk\\upstream\\bin\\clang.exe" -cc1 -triple wasm32-unknown-emscripten -O3 -emit-obj -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name main.c -mrelocation-model pic -pic-level 2 -mframe-pointer=none -ffp-contract=on -fno-rounding-math -mconstructor-aliases -target-feature +mutable-globals -target-cpu generic -debugger-tuning=gdb "-fdebug-compilation-dir=C:\\Users\\nabca\\Documents\\src\\misc\\wasmldtest" -v "-fcoverage-compilation-dir=C:\\Users\\nabca\\Documents\\src\\misc\\wasmldtest" -resource-dir "C:\\dev\\emsdk\\upstream\\lib\\clang\\22" -D EMSCRIPTEN -isysroot "C:\\dev\\emsdk\\upstream\\emscripten\\cache\\sysroot" -internal-isystem "C:\\dev\\emsdk\\upstream\\lib\\clang\\22\\include" -internal-isystem "C:\\dev\\emsdk\\upstream\\emscripten\\cache\\sysroot/include/wasm32-emscripten" -internal-isystem "C:\\dev\\emsdk\\upstream\\emscripten\\cache\\sysroot/include" -ferror-limit 19 -fmessage-length=120 -fvisibility=default -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fignore-exceptions -fcolor-diagnostics -vectorize-loops -vectorize-slp "-iwithsysroot/include\\fakesdl" "-iwithsysroot/include\\compat" -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr -o "C:\\Users\\nabca\\AppData\\Local\\Temp\\emscripten_temp_fnr2y61r\\main.o" -x c main.c
clang -cc1 version 22.0.0git based upon LLVM 22.0.0git default target x86_64-pc-windows-msvc
ignoring nonexistent directory "C:\dev\emsdk\upstream\emscripten\cache\sysroot/include/wasm32-emscripten"
#include "..." search starts here:
#include <...> search starts here:
 C:\dev\emsdk\upstream\emscripten\cache\sysroot/include\fakesdl
 C:\dev\emsdk\upstream\emscripten\cache\sysroot/include\compat
 C:\dev\emsdk\upstream\lib\clang\22\include
 C:\dev\emsdk\upstream\emscripten\cache\sysroot/include
End of search list.
 "C:/dev/emsdk/upstream/bin\clang.exe" --version
 "C:/dev/emsdk/upstream/bin\wasm-ld.exe" -o out/main.wasm -Bdynamic 'C:\Users\nabca\AppData\Local\Temp\emscripten_temp_fnr2y61r\main.o' '-LC:\dev\emsdk\upstream\emscripten\cache\sysroot\lib\wasm32-emscripten\pic' '-LC:\dev\emsdk\upstream\emscripten\src\lib' -lGL-getprocaddr -lal -lhtml5 -lstubs -lnoexit -lc -ldlmalloc -lcompiler_rt -lc++-noexcept -lc++abi-noexcept -lsockets -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr 'C:\Users\nabca\AppData\Local\Temp\tmpu410uy5glibemscripten_js_symbols.so' --strip-debug --export=__stack_pointer --export=_emscripten_stack_alloc --export=__wasm_call_ctors --export=setThrew --export=_emscripten_stack_restore --export=emscripten_stack_get_current --export=_emscripten_find_dylib --export=emscripten_get_sbrk_ptr --export=__heap_base --export=calloc --export-if-defined=__start_em_asm --export-if-defined=__stop_em_asm --export-if-defined=__start_em_lib_deps --export-if-defined=__stop_em_lib_deps --export-if-defined=__start_em_js --export-if-defined=__stop_em_js --export-if-defined=main --export-if-defined=__main_argc_argv --experimental-pic --unresolved-symbols=import-dynamic --export-table --growable-table -z stack-size=65536 --no-growable-memory --initial-heap=16777216 --no-entry --table-base=1 --global-base=1024
 "C:/dev/emsdk/upstream/bin\llvm-objcopy.exe" out/main.wasm out/main.wasm '--remove-section=.debug*' --remove-section=producers --remove-section=name
 "C:/dev/emsdk/node/22.16.0_64bit/bin/node.exe" 'C:\dev\emsdk\upstream\emscripten\tools\compiler.mjs' -
 "C:/dev/emsdk/upstream\bin\wasm-opt" --strip-target-features --post-emscripten -O3 --low-memory-unused --zero-filled-memory --pass-arg=directize-initial-contents-immutable --no-stack-ir out/main.wasm -o out/main.wasm --mvp-features --enable-bulk-memory --enable-bulk-memory-opt --enable-call-indirect-overlong --enable-multivalue --enable-mutable-globals --enable-nontrapping-float-to-int --enable-reference-types --enable-sign-ext
 "C:/dev/emsdk/node/22.16.0_64bit/bin/node.exe" 'C:\dev\emsdk\upstream\emscripten\tools\acorn-optimizer.mjs' 'C:\Users\nabca\AppData\Local\Temp\emscripten_temp_fnr2y61r\main.js' AJSDCE --minify-whitespace -o 'C:\Users\nabca\AppData\Local\Temp\emscripten_temp_fnr2y61r\main.jso1.js'
 "C:/dev/emsdk/node/22.16.0_64bit/bin/node.exe" 'C:\dev\emsdk\upstream\emscripten\tools\acorn-optimizer.mjs' 'C:\Users\nabca\AppData\Local\Temp\emcc_acorn_info_18nw1eiu.js' emitDCEGraph --no-print
 "C:/dev/emsdk/upstream\bin\wasm-metadce" '--graph-file=C:\Users\nabca\AppData\Local\Temp\emcc_dce_graph_yiq193w4.json' --optimize-level=3 --shrink-level=0 --optimize-stack-ir out/main.wasm -o out/main.wasm --mvp-features --enable-bulk-memory --enable-bulk-memory-opt --enable-call-indirect-overlong --enable-multivalue --enable-mutable-globals --enable-nontrapping-float-to-int --enable-reference-types --enable-sign-ext
 "C:/dev/emsdk/node/22.16.0_64bit/bin/node.exe" 'C:\dev\emsdk\upstream\emscripten\tools\acorn-optimizer.mjs' 'C:\Users\nabca\AppData\Local\Temp\emcc_acorn_info_in7iw816.js' applyDCEGraphRemovals --minify-whitespace -o 'C:\Users\nabca\AppData\Local\Temp\emscripten_temp_fnr2y61r\main.jso2.js'
 "C:/dev/emsdk/node/22.16.0_64bit/bin/node.exe" 'C:\dev\emsdk\upstream\emscripten\tools\acorn-optimizer.mjs' 'C:\Users\nabca\AppData\Local\Temp\emscripten_temp_fnr2y61r\main.jso2.js' AJSDCE --minify-whitespace -o 'C:\Users\nabca\AppData\Local\Temp\emscripten_temp_fnr2y61r\main.jso3.js'

wasm-objdump output

> wasm-objdump -x -j Export out/main.wasm

main.wasm:      file format wasm 0x1

Section Details:

Export[13]:
 - memory[0] -> "memory"
 - func[53] <__wasm_call_ctors> -> "__wasm_call_ctors"
 - table[0] -> "__indirect_function_table"
 - func[52] <main> -> "main"
 - func[38] <__dl_seterr> -> "__dl_seterr"
 - func[28] <_emscripten_find_dylib> -> "_emscripten_find_dylib"
 - func[20] <calloc> -> "calloc"
 - func[45] <emscripten_get_sbrk_ptr> -> "emscripten_get_sbrk_ptr"
 - global[1] -> "__heap_base"
 - func[41] <setThrew> -> "setThrew"
 - func[44] <_emscripten_stack_restore> -> "_emscripten_stack_restore"
 - func[43] <_emscripten_stack_alloc> -> "_emscripten_stack_alloc"
 - func[42] <emscripten_stack_get_current> -> "emscripten_stack_get_current"

main.c -- works perfectly fine with O2 or -sMAIN_MODULE=1

#include <stdio.h>
#include <dlfcn.h>

typedef void (*testfunc_f)(int*);

int main(void) {
    printf("Main module loaded.\n");
    
    void *mod = dlopen("./side.so", RTLD_NOW);
    if (mod == NULL) {
        printf("Could not load side module.\n");
        return 1;
    }

    printf("Resolve test_func...\n");
    testfunc_f f = dlsym(mod, "test_func");
    if (f == NULL) {
        printf("Could not get exported function.\n");
        return 1;
    }
    
    printf("Run test_func...\n");
    int a = 10;
    f(&a);
    printf("value: %i\n", a);
 
    printf("Done.\n");
       
    dlclose(mod);
    return 0;
}

and side.c, just to be thorough

#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
void test_func(int *a) {
    int v = *a;
    for (int i = 0; i < v; ++i) {
        *a += i;
    }
}

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