emscripten-core / emscripten

Emscripten: An LLVM-to-WebAssembly Compiler
Other
25.74k stars 3.3k forks source link

IF YOU ARE EXPERIENCING SYMBOLS BEING STRIPPED NO MATTER WHAT YOU DO AND ARE GETTING LINKER ERRORS, READ THIS #22730

Open zackees opened 1 week ago

zackees commented 1 week ago

I've been creating an empscripten WASM target for FastLED.

earlier this week today i've been intensively investigating symbols not showing up.

Let me just skip right to the point

Clang is stripping out your symbols before they are getting to the linker. Our exports were in one .cpp file.

Here's what's happening: clang is really aggressive in eliminating object files that aren't being referenced by other objects/libs (as of trzeci/emscripten:1.39.17-upstream).

EMSCRIPTEN_KEEPALIVE appears to be powerless here.. Telling the linker the symbols exist does nothing in orphaned object files. Clang will tree shake the exports out.

Here's a quick solution that worked for this project.

Happy coding!

Repro Case

git clone github.com/fastled/fastled

Check out this commit. 5d0c06e3e8e2a6dc19b21263c2360839ac818c2c

Then run this command: uv run ci/wasm_compile.py -b examples/wasm

When the build is done a browser window will pop open and you'll see this, missing symbols for extern_setup and extern_loop:

image

Anytime after we applied the fix, you should see something like this:

image

Previous issue(s): https://github.com/emscripten-core/emscripten/issues/6233

sbc100 commented 6 days ago

EMSCRIPTEN_KEEPALIVE is powerless if the symbol is inside a object the is otherwise unneeded and lives in an object file.

In addition to the method you mention have a few other options:

  1. Use the command line instead (i.e. add them to EXPORTED_FUNCTIONS on the command line).
  2. Use -Wl,--whole-archive -lfoo -Wl,--no-whole-archive instead of -lfoo. This will force all object files in the library to be included.
zackees commented 4 days ago

It's a platformio wasm compile of FastLED. My scons knowledge is limited, here is my platformio.ini extra_flags.py if someone knows what I'm doing wrong:

import os

# Global variable to control WASM output (0 for asm.js, 1 for WebAssembly)
# It seems easier to load the program as a pure JS file, so we will use asm.js
# right now as a test.
USE_WASM = 1

Import("env", "projenv")

# projenv is used for compiling individual files, env for linking
# libraries have their own env in env.GetLibBuilders()

# this is kinda hacky but let's just replace the default toolchain
# with emscripten. the right way to do this would be to create a
# SCons toolchain file for emscripten, and platformio platform for
# WebAssembly, but this is easier for now

projenv.Replace(CC="emcc", CXX="em++", LINK="em++", AR="emar", RANLIB="emranlib")

env.Replace(CC="emcc", CXX="em++", LINK="em++", AR="emar", RANLIB="emranlib")

wasmflags = [
    "--oformat=js",
    "-DFASTLED_ENGINE_EVENTS_MAX_LISTENERS=50",
    "-DFASTLED_USE_PROGMEM=0",
    "-s",
    "EXPORTED_RUNTIME_METHODS=['ccall','cwrap']",
    "-s",
    "ALLOW_MEMORY_GROWTH=1",
    "-Oz",
    "-s",
    "EXPORTED_FUNCTIONS=['_malloc','_free','_extern_setup','_extern_loop']",
    "--bind",
    "-s",
    "INITIAL_MEMORY=1073741824",
    "--no-entry",
    # Enable C++17 with GNU extensions.
    "-std=gnu++17",
    "-fpermissive",
    "-Wno-constant-logical-operand",
    "-Wnon-c-typedef-for-linkage",
    #"-s",
    #"STACK_SIZE=5368709",
    f"-sWASM={USE_WASM}",
    "-s", f"WASM={USE_WASM}",
    #"-s", "LEGACY_VM_SUPPORT=1"
]

export_name = env.GetProjectOption("custom_wasm_export_name", "")
if export_name:
    wasmflags += [
        "-s",
        "MODULARIZE=1",
        "-s",
        f"EXPORT_NAME='{export_name}'",
        "-o",
        f"{env.subst('$BUILD_DIR')}/{export_name}.js",
    ]

env.Append(LINKFLAGS=wasmflags)

# Pass flags to the other Project Dependencies (libraries)
for lb in env.GetLibBuilders():
    lb.env.Replace(CC="emcc", CXX="em++", LINK="em++", AR="emar", RANLIB="emranlib")