Skip to content

[Bug] --emit-tsd Fails in Debug Builds with Multiple Binding Files Using Same Module Name #24623

Open
@NotAgithubAdmin

Description

@NotAgithubAdmin

Description

When building with Emscripten using multiple C++ binding files that share the same EMSCRIPTEN_BINDINGS module name, the build fails only if both debug flags (-O0 -g) and TypeScript generation (--emit-tsd) are enabled. Other combinations work as expected.

Version of emscripten/emsdk:

emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 4.0.7 (8dc91db)
clang version 21.0.0git (https:/github.com/llvm/llvm-project 57025b42c43b2f14f7e58692bc19cd53d1b8a45e)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: C:\PROJECT_PATH\emsdk\upstream\bin

Failing command line in full:

emcc first_bindings.cpp second_bindings.cpp \    
 -std=c++20 -O0 -g -o debug_output.js --bind \
-sMODULARIZE=1 -sEXPORT_NAME="TestModule" -sWASM=1 -sEXPORT_ES6=1 \
--emit-tsd=debug_output.d.ts -v

Full link command and output with -v appended:

"C:/SOME_PROJ/emsdk/upstream/bin\clang.exe" -target wasm32-unknown-emscripten -fignore-exceptions -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr --sysroot=C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot -DEMSCRIPTEN -Xclang -iwithsysroot/include\fakesdl -Xclang -iwithsysroot/include\compat -std=c++20 -O0 -g3 -v -c first_bindings.cpp -o C:\Users\USERNAME\AppData\Local\Temp\emscripten_temp_br1zjkal\first_bindings_0.o
clang version 21.0.0git (https:/github.com/llvm/llvm-project 57025b42c43b2f14f7e58692bc19cd53d1b8a45e)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: C:\SOME_PROJ\emsdk\upstream\bin
(in-process)
"C:\SOME_PROJ\emsdk\upstream\bin\clang.exe" -cc1 -triple wasm32-unknown-emscripten -emit-obj -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name first_bindings.cpp -mrelocation-model static -mframe-pointer=none -ffp-contract=on -fno-rounding-math -mconstructor-aliases -target-cpu generic -fvisibility=hidden -debug-info-kind=constructor -dwarf-version=4 -debugger-tuning=gdb "-fdebug-compilation-dir=C:\SOME_PROJ\TEST\min_repro_git_issue" -v "-fcoverage-compilation-dir=C:\SOME_PROJ\TEST\min_repro_git_issue" -resource-dir "C:\SOME_PROJ\emsdk\upstream\lib\clang\21" -D EMSCRIPTEN -isysroot "C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot" -internal-isystem "C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include/wasm32-emscripten/c++/v1" -internal-isystem "C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include/c++/v1" -internal-isystem "C:\SOME_PROJ\emsdk\upstream\lib\clang\21\include" -internal-isystem "C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include/wasm32-emscripten" -internal-isystem "C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include" -O0 -std=c++20 -fdeprecated-macro -ferror-limit 19 -fmessage-length=130 -fgnuc-version=4.2.1 -fno-implicit-modules -fskip-odr-check-in-gmf -fcxx-exceptions -fignore-exceptions -fexceptions -fcolor-diagnostics "-iwithsysroot/include\fakesdl" "-iwithsysroot/include\compat" -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr -o "C:\Users\USERNAME\AppData\Local\Temp\emscripten_temp_br1zjkal\first_bindings_0.o" -x c++ first_bindings.cpp
clang -cc1 version 21.0.0git based upon LLVM 21.0.0git default target x86_64-pc-windows-msvc
ignoring nonexistent directory "C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include/wasm32-emscripten/c++/v1"
ignoring nonexistent directory "C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include/wasm32-emscripten"
#include "..." search starts here:
#include <...> search starts here:
C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include\fakesdl
C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include\compat
C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include/c++/v1
C:\SOME_PROJ\emsdk\upstream\lib\clang\21\include
C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include
End of search list.
"C:/SOME_PROJ/emsdk/upstream/bin\clang.exe" -target wasm32-unknown-emscripten -fignore-exceptions -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr --sysroot=C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot -DEMSCRIPTEN -Xclang -iwithsysroot/include\fakesdl -Xclang -iwithsysroot/include\compat -std=c++20 -O0 -g3 -v -c second_bindings.cpp -o C:\Users\USERNAME\AppData\Local\Temp\emscripten_temp_br1zjkal\second_bindings_1.o
clang version 21.0.0git (https:/github.com/llvm/llvm-project 57025b42c43b2f14f7e58692bc19cd53d1b8a45e)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: C:\SOME_PROJ\emsdk\upstream\bin
(in-process)
"C:\SOME_PROJ\emsdk\upstream\bin\clang.exe" -cc1 -triple wasm32-unknown-emscripten -emit-obj -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name second_bindings.cpp -mrelocation-model static -mframe-pointer=none -ffp-contract=on -fno-rounding-math -mconstructor-aliases -target-cpu generic -fvisibility=hidden -debug-info-kind=constructor -dwarf-version=4 -debugger-tuning=gdb "-fdebug-compilation-dir=C:\SOME_PROJ\TEST\min_repro_git_issue" -v "-fcoverage-compilation-dir=C:\SOME_PROJ\TEST\min_repro_git_issue" -resource-dir "C:\SOME_PROJ\emsdk\upstream\lib\clang\21" -D EMSCRIPTEN -isysroot "C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot" -internal-isystem "C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include/wasm32-emscripten/c++/v1" -internal-isystem "C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include/c++/v1" -internal-isystem "C:\SOME_PROJ\emsdk\upstream\lib\clang\21\include" -internal-isystem "C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include/wasm32-emscripten" -internal-isystem "C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include" -O0 -std=c++20 -fdeprecated-macro -ferror-limit 19 -fmessage-length=130 -fgnuc-version=4.2.1 -fno-implicit-modules -fskip-odr-check-in-gmf -fcxx-exceptions -fignore-exceptions -fexceptions -fcolor-diagnostics "-iwithsysroot/include\fakesdl" "-iwithsysroot/include\compat" -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr -o "C:\Users\USERNAME\AppData\Local\Temp\emscripten_temp_br1zjkal\second_bindings_1.o" -x c++ second_bindings.cpp
clang -cc1 version 21.0.0git based upon LLVM 21.0.0git default target x86_64-pc-windows-msvc
ignoring nonexistent directory "C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include/wasm32-emscripten/c++/v1"
ignoring nonexistent directory "C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include/wasm32-emscripten"
#include "..." search starts here:
#include <...> search starts here:
C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include\fakesdl
C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include\compat
C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include/c++/v1
C:\SOME_PROJ\emsdk\upstream\lib\clang\21\include
C:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot/include
End of search list.
"C:/SOME_PROJ/emsdk/upstream/bin\clang.exe" --version
"C:/SOME_PROJ/emsdk/upstream/bin\wasm-ld.exe" -o build/debug_output.wasm C:\Users\USERNAME\AppData\Local\Temp\emscripten_temp_br1zjkal\first_bindings_0.o C:\Users\USERNAME\AppData\Local\Temp\emscripten_temp_br1zjkal\second_bindings_1.o -lembind-rtti -LC:\SOME_PROJ\emsdk\upstream\emscripten\cache\sysroot\lib\wasm32-emscripten -LC:\SOME_PROJ\emsdk\upstream\emscripten\src\lib -lGL-getprocaddr -lal -lhtml5 -lstubs-debug -lnoexit -lc-debug -ldlmalloc-debug -lcompiler_rt -lc++-noexcept -lc++abi-debug-noexcept -lsockets -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr C:\Users\USERNAME\AppData\Local\Temp\tmpeaxpcp2vlibemscripten_js_symbols.so --export=emscripten_stack_get_end --export=emscripten_stack_get_free --export=emscripten_stack_get_base --export=emscripten_stack_get_current --export=emscripten_stack_init --export=_emscripten_stack_alloc --export=__getTypeName --export=__wasm_call_ctors --export=_emscripten_stack_restore --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 --export-if-defined=fflush --export-table -z stack-size=65536 --no-growable-memory --initial-heap=16777216 --no-entry --stack-first --table-base=1
"C:/SOME_PROJ/emsdk/upstream/bin\llvm-objcopy.exe" build/debug_output.wasm build/debug_output.wasm --remove-section=producers
"C:/SOME_PROJ/emsdk/node/20.18.0_64bit/bin/node.exe" C:\SOME_PROJ\emsdk\upstream\emscripten\tools\compiler.mjs -
"C:/SOME_PROJ/emsdk/node/20.18.0_64bit/bin/node.exe" C:\SOME_PROJ\emsdk\upstream\emscripten\tools\compiler.mjs -
"C:/SOME_PROJ/emsdk/node/20.18.0_64bit/bin/node.exe" C:\Users\USERNAME\AppData\Local\Temp\emscripten_temp_br1zjkal\tsgen.js
C:\Users\USERNAME\AppData\Local\Temp\emscripten_temp_br1zjkal\tsgen.js:940
var throwBindingError = (message) => { throw new BindingError(message); };
^

BindingError: Cannot register type 'someValArr' twice
at throwBindingError (C:\Users\USERNAME\AppData\Local\Temp\emscripten_temp_br1zjkal\tsgen.js:940:48)
at sharedRegisterType (C:\Users\USERNAME\AppData\Local\Temp\emscripten_temp_br1zjkal\tsgen.js:951:11)
at registerType (C:\Users\USERNAME\AppData\Local\Temp\emscripten_temp_br1zjkal\tsgen.js:965:14)
at onComplete (C:\Users\USERNAME\AppData\Local\Temp\emscripten_temp_br1zjkal\tsgen.js:889:11)
at C:\Users\USERNAME\AppData\Local\Temp\emscripten_temp_br1zjkal\tsgen.js:908:15
at C:\Users\USERNAME\AppData\Local\Temp\emscripten_temp_br1zjkal\tsgen.js:961:35
at Array.forEach ()
at sharedRegisterType (C:\Users\USERNAME\AppData\Local\Temp\emscripten_temp_br1zjkal\tsgen.js:961:19)
at registerType (C:\Users\USERNAME\AppData\Local\Temp\emscripten_temp_br1zjkal\tsgen.js:965:14)
at registerIntegerType (C:\Users\USERNAME\AppData\Local\Temp\emscripten_temp_br1zjkal\tsgen.js:1402:7)

Node.js v20.18.0
emcc: error: 'C:/SOME_PROJ/emsdk/node/20.18.0_64bit/bin/node.exe C:\Users\USERNAME\AppData\Local\Temp\emscripten_temp_br1zjkal\tsgen.js' failed (returned 1)

Repro

first_bindings.cpp:

#include <emscripten/bind.h>
#include <array>

using namespace emscripten;

std::array<unsigned char, 16> createArray() {
    return {0, 1};
}

EMSCRIPTEN_BINDINGS(dup_module_name) {
    value_array<std::array<unsigned char, 16>>("someValArr")
        .element(emscripten::index<0>())
        .element(emscripten::index<1>());

    function("createArray", &createArray);
}

second_bindings.cpp:

#include <emscripten/bind.h>

using namespace emscripten;

enum class TestEnum {
    ZERO = 0,
    ONE = 1
};

std::string TestEnumToString(TestEnum domain) {
    switch(domain) {
        case TestEnum::ZERO: return "ZERO";
        case TestEnum::ONE: return "ONE";
        default: return "UNKNOWN";
    }
}

EMSCRIPTEN_BINDINGS(dup_module_name) {
    enum_<TestEnum>("TestEnum")
        .value("ZERO", TestEnum::ZERO)
        .value("ONE", TestEnum::ONE);
    function("TestEnumToString", &TestEnumToString);
}

test_bug.sh:
run all tests: test_bug.sh
run a specific test: test_bug.sh #

#!/usr/bin/env bash

BUILD_DIR="build"

# Clean up build directory
if [ -d "$BUILD_DIR" ]; then
    rm -rf "$BUILD_DIR"
fi
mkdir -p "$BUILD_DIR"

# Get absolute path to build directory for --emit-tsd
BUILD_DIR_ABS=$(cd "$BUILD_DIR" && pwd)

# Parse argument for test selection
RUN_ALL=true
RUN_TEST1=false
RUN_TEST2=false
RUN_TEST3=false

if [ $# -gt 0 ]; then
    RUN_ALL=false
    for arg in "$@"; do
        case $arg in
            1) RUN_TEST1=true ;;
            2) RUN_TEST2=true ;;
            3) RUN_TEST3=true ;;
            *) echo "Unknown test: $arg"; exit 1 ;;
        esac
    done
else
    RUN_TEST1=true
    RUN_TEST2=true
    RUN_TEST3=true
fi

# All output files go into $BUILD_DIR, but script runs from project root
echo "=== Minimal Reproducible Example for Emscripten TypeScript Generation Bug ==="
echo ""

if $RUN_TEST1; then
    echo "1. Testing release build with multiple binding files (should work):"
    emcc first_bindings.cpp second_bindings.cpp \
      -std=c++20 \
      -O2 \
      -o "$BUILD_DIR/release_output.js" \
      --bind \
      -sMODULARIZE=1 \
      -sEXPORT_NAME="TestModule" \
      -sWASM=1 \
      -sEXPORT_ES6=1 \
      --emit-tsd="$BUILD_DIR_ABS/release_output.d.ts" \
      -v

    if [ $? -eq 0 ]; then
        echo "✅ Release build succeeded"
    else
        echo "❌ Release build failed"
    fi
    echo ""
fi

if $RUN_TEST2; then
    echo "2. Testing debug build with TypeScript generation and multiple binding files (might fail):"
    emcc first_bindings.cpp second_bindings.cpp \
      -std=c++20 \
      -O0 \
      -g \
      -o "$BUILD_DIR/debug_output.js" \
      --bind \
      -sMODULARIZE=1 \
      -sEXPORT_NAME="TestModule" \
      -sWASM=1 \
      -sEXPORT_ES6=1 \
      --emit-tsd="$BUILD_DIR_ABS/debug_output.d.ts" \
      -v

    if [ $? -eq 0 ]; then
        echo "✅ Debug build succeeded (issue might be more complex)"
    else
        echo "❌ Debug build failed with TypeScript generation (this reproduces the bug)"
    fi
    echo ""
fi

if $RUN_TEST3; then
    echo "3. Testing debug build WITHOUT TypeScript generation:"
    emcc first_bindings.cpp second_bindings.cpp \
      -std=c++20 \
      -O0 \
      -g \
      -o "$BUILD_DIR/debug_no_ts.js" \
      --bind \
      -sMODULARIZE=1 \
      -sEXPORT_NAME="TestModule" \
      -sWASM=1 \
      -sEXPORT_ES6=1 \
      -v

    if [ $? -eq 0 ]; then
        echo "✅ Debug build without TypeScript generation succeeded"
    else
        echo "❌ Debug build without TypeScript generation failed"
    fi
    echo ""
fi

echo "=== Summary ==="
echo "This test uses multiple binding files with the same EMSCRIPTEN_BINDINGS module name."
echo ""
echo "If test 2 fails but tests 1 and 3 succeed, then the issue is specifically:"
echo "- Multiple binding files with same module name"
echo "- Basic debug flags (-O0 -g)" 
echo "- Combined with TypeScript generation (--emit-tsd)"

Metadata

Metadata

Assignees

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