emscripten-core / emscripten

Emscripten: An LLVM-to-WebAssembly Compiler
Other
25.37k stars 3.25k forks source link

OpenAL linking is mandatory #19818

Open icculus opened 1 year ago

icculus commented 1 year ago

Version of emscripten/emsdk:

emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.41 (71634e036d20209a5d81c2b2171e145b44de1e12)
clang version 17.0.0 (https://github.com/llvm/llvm-project 88421ea973916e60c34beb26597a5fc33f83dd8f)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /home/icculus/projects/dragonruby/build-tools/host/Linux-amd64/emsdk/upstream/bin

Failing command line in full:

/home/icculus/projects/dragonruby/build-tools/host/Linux-amd64/emsdk/upstream/emscripten/emcc -O3 -DNDEBUG -flto=thin -Wl,-u,htons -Wl,-u,ntohs -Wl,-u,htonl  -pthread -s USE_SDL=0 -s MAIN_MODULE -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s MAXIMUM_MEMORY=1gb -s ASSERTIONS=0 -s EXPORTED_RUNTIME_METHODS=['addRunDependency','removeRunDependency'] -s DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=['$autoResumeAudioContext','$dynCall'] -lidbfs.js CMakeFiles/dragonruby.dir/audio.c.o CMakeFiles/dragonruby.dir/cvar.c.o CMakeFiles/dragonruby.dir/dragonruby_ffi.c.o CMakeFiles/dragonruby.dir/draw.c.o CMakeFiles/dragonruby.dir/fontatlas.c.o CMakeFiles/dragonruby.dir/fn.c.o CMakeFiles/dragonruby.dir/outputs.c.o CMakeFiles/dragonruby.dir/geometry.c.o CMakeFiles/dragonruby.dir/array.c.o CMakeFiles/dragonruby.dir/hash.c.o CMakeFiles/dragonruby.dir/matrix.c.o CMakeFiles/dragonruby.dir/ffi.c.o CMakeFiles/dragonruby.dir/game.c.o CMakeFiles/dragonruby.dir/http_server.c.o CMakeFiles/dragonruby.dir/janitor.c.o CMakeFiles/dragonruby.dir/logging.c.o CMakeFiles/dragonruby.dir/main.c.o CMakeFiles/dragonruby.dir/networking.c.o CMakeFiles/dragonruby.dir/rendercontext.c.o CMakeFiles/dragonruby.dir/renderstream.c.o CMakeFiles/dragonruby.dir/texture.c.o CMakeFiles/dragonruby.dir/tests.c.o CMakeFiles/dragonruby.dir/threadstream.c.o CMakeFiles/dragonruby.dir/threadstream_ruby.c.o CMakeFiles/dragonruby.dir/util.c.o CMakeFiles/dragonruby.dir/xml.c.o CMakeFiles/dragonruby.dir/vr.c.o CMakeFiles/dragonruby.dir/mojoAL/mojoal.c.o CMakeFiles/dragonruby.dir/physfs/extras/physfsrwops.c.o CMakeFiles/dragonruby.dir/yxml/yxml.c.o CMakeFiles/dragonruby.dir/cJSON/cJSON.c.o CMakeFiles/dragonruby.dir/main_ruby.c.o -o dragonruby-wasm.js  /home/icculus/projects/dragonruby/SDL/lib/emscripten/libSDL2.a  libmruby_core.a  libmruby_gems.a  libphysfs.a

Full link command and output with -v appended:

/home/icculus/projects/dragonruby/build-tools/host/Linux-amd64/emsdk/upstream/bin/wasm-ld -o dragonruby-wasm.wasm --whole-archive -u htons -u ntohs -u htonl CMakeFiles/dragonruby.dir/audio.c.o CMakeFiles/dragonruby.dir/cvar.c.o CMakeFiles/dragonruby.dir/dragonruby_ffi.c.o CMakeFiles/dragonruby.dir/draw.c.o CMakeFiles/dragonruby.dir/fontatlas.c.o CMakeFiles/dragonruby.dir/fn.c.o CMakeFiles/dragonruby.dir/outputs.c.o CMakeFiles/dragonruby.dir/geometry.c.o CMakeFiles/dragonruby.dir/array.c.o CMakeFiles/dragonruby.dir/hash.c.o CMakeFiles/dragonruby.dir/matrix.c.o CMakeFiles/dragonruby.dir/ffi.c.o CMakeFiles/dragonruby.dir/game.c.o CMakeFiles/dragonruby.dir/http_server.c.o CMakeFiles/dragonruby.dir/janitor.c.o CMakeFiles/dragonruby.dir/logging.c.o CMakeFiles/dragonruby.dir/main.c.o CMakeFiles/dragonruby.dir/networking.c.o CMakeFiles/dragonruby.dir/rendercontext.c.o CMakeFiles/dragonruby.dir/renderstream.c.o CMakeFiles/dragonruby.dir/texture.c.o CMakeFiles/dragonruby.dir/tests.c.o CMakeFiles/dragonruby.dir/threadstream.c.o CMakeFiles/dragonruby.dir/threadstream_ruby.c.o CMakeFiles/dragonruby.dir/util.c.o CMakeFiles/dragonruby.dir/xml.c.o CMakeFiles/dragonruby.dir/vr.c.o CMakeFiles/dragonruby.dir/mojoAL/mojoal.c.o CMakeFiles/dragonruby.dir/physfs/extras/physfsrwops.c.o CMakeFiles/dragonruby.dir/yxml/yxml.c.o CMakeFiles/dragonruby.dir/cJSON/cJSON.c.o CMakeFiles/dragonruby.dir/main_ruby.c.o /home/icculus/projects/dragonruby/SDL/lib/emscripten/libSDL2.a libmruby_core.a libmruby_gems.a libphysfs.a -L/home/icculus/projects/dragonruby/build-tools/host/Linux-amd64/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/thinlto-pic /home/icculus/projects/dragonruby/build-tools/host/Linux-amd64/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/thinlto-pic/crtbegin.o -lGL-mt -lal -lhtml5 -lbulkmemory -lstubs -lnoexit -lc-mt -ldlmalloc-mt -lcompiler_rt-mt -lc++-mt-noexcept -lc++abi-mt-noexcept -lsockets-mt --no-whole-archive -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr --export-if-defined=setTempRet0 --export-if-defined=setThrew --export-if-defined=malloc --export-if-defined=_emscripten_thread_init --export-if-defined=_emscripten_thread_free_data --export-if-defined=_emscripten_thread_exit --export-if-defined=__errno_location --export-if-defined=htons --export-if-defined=ntohs --export-if-defined=memcpy --export-if-defined=free --export-if-defined=htonl --export-if-defined=_emscripten_timeout --export-if-defined=_emscripten_run_in_main_runtime_thread_js --export-if-defined=__cxa_is_pointer_type --export-if-defined=__dl_seterr --export-if-defined=__cxa_can_catch --export-if-defined=__cxa_increment_exception_refcount --export-if-defined=__cxa_decrement_exception_refcount --export-if-defined=fileno --export-if-defined=emscripten_builtin_memalign --export-if-defined=memcmp --export-if-defined=sleep --export-if-defined=pthread_self --export-if-defined=_emscripten_proxy_dlsync_async --export-if-defined=_emscripten_proxy_dlsync /tmp/tmpokggryf3libemscripten_js_symbols.so --import-memory --shared-memory --strip-debug --export-dynamic --export-if-defined=main --export-if-defined=_emscripten_thread_init --export-if-defined=_emscripten_thread_exit --export-if-defined=_emscripten_thread_crashed --export-if-defined=_emscripten_tls_init --export-if-defined=pthread_self --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_argc_argv --export-if-defined=__wasm_apply_data_relocs --export=malloc --export=setThrew --export=stackSave --export=stackRestore --export=stackAlloc --export=__errno_location --export=emscripten_dispatch_to_thread_ --export=_emscripten_thread_free_data --export=emscripten_main_runtime_thread_id --export=emscripten_main_thread_process_queued_calls --export=_emscripten_run_in_main_runtime_thread_js --export=emscripten_stack_set_limits --export=_emscripten_dlsync_self --export=_emscripten_dlsync_self_async --export=_emscripten_proxy_dlsync --export=_emscripten_proxy_dlsync_async --export=__dl_seterr --export=__get_temp_ret --export=__set_temp_ret --export=__wasm_call_ctors --experimental-pic -pie -z stack-size=65536 --initial-memory=16777216 --no-entry --max-memory=1073741824

Details:

It looks like (in Emscripten 3.1.41 at least), OpenAL is always specified on the linker command line (-lal) when using -s MAIN_MODULE (or maybe always, and dead-code elimination removes it or other OpenAL implementations get favored, idk). This particular program uses MojoAL, which provides a full OpenAL 1.1 implementation in a single C file, so naturally we get a symbol conflict here vs Emscripten's included OpenAL:

wasm-ld: error: duplicate symbol: alcGetProcAddress
>>> defined in CMakeFiles/dragonruby.dir/mojoAL/mojoal.c.o
>>> defined in /home/icculus/projects/dragonruby/build-tools/host/Linux-amd64/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/thinlto-pic/libal.a(al.o)

My assumption is the the --whole-archive option that -s MAIN_MODULE includes is what triggers this error. Without MAIN_MODULE this program links successfully, because it seems to ignore the extra OpenAL library (or -lal isn't present in that case, perhaps?).

All of this to say: it would be extremely nice if there was an -s USE_OPENAL=0 option to match the existing USE_SDL and friends, to not attempt to link an OpenAL implementation by default.

sbc100 commented 1 year ago

Yes we should make -sUSE_OPENAL=0 as you describe. I will upload the PR for that.

In the mean time you can control the automatic inclusion of libraries using -sAUTO_NATIVE_LIBRARIES=0 and/or -sAUTO_JS_LIBRARIES=0. Both these settings are enabled when you build with -sSTRICT.. and they basically mean that you need to link explictly against what you need. In the long run I hope we can make these the default. For now they are both enabled by default to support backward compat.

You might also want to consider -sMAIN_MODULE=2 which will avoid the --whole-archive flags and just include what you need/use. Using -sMAIN_MODULE=1 results in huge binaries since it can't DCE anything. I strongly recommend avoid that if you can. What is you use case for dynamic linking BTW?

sbc100 commented 1 year ago

Oh, wait... there is no existing USE_OPENAL setting, so rather than create a new setting I suggest -sAUTO_JS_LIBRARIES=0

icculus commented 1 year ago

(This was all great advice, thank you! I'll experiment over here.)

What is you use case for dynamic linking BTW?

We have a product called DragonRuby Game Toolkit, a game engine written in C, that lets you build 2D games in Ruby and trivially deploy it to a bunch of platforms. One of them is, of course, the web, so we have an Emscripten-based build of the engine.

One of the features is you can write modules as native code, in a shared library, and we'll load it at runtime and hook it up for calling from Ruby, for when you need something the engine doesn't directly supply or more speed or whatever, and this hasn't been supported in our Emscripten build so far. I got it working last night, so here's the worst version of Hunt The Wumpus ever using DragonRuby and calling into a .wasm shared library to do text-to-speech:

https://icculus.org/~icculus/emscripten/wumpus-tts-1.0/

(The creature is two moves north, two moves east, and then he'll be one room to the north of you. I wrote it in an hour, be gentle. :) )

The shared library interface gets passed a struct with a bunch of function pointers in it that represent a basic subset of C runtime functions and other functionality, so in theory it shouldn't need the entire kitchen sink available by default in the main module, as it should call through those function pointers to touch any runtime symbols it wants. We'll probably make adjustments to the main module as things explode, or when some library needs C++ and we haven't exported necessary symbols for exception handling, or whatnot, but we'll experiment with your suggestions and try to find the right balance.

Thanks again!

sbc100 commented 1 year ago

Oh, wait... there is no existing USE_OPENAL setting, so rather than create a new setting I suggest -sAUTO_JS_LIBRARIES=0

Sorry.. I meant -sAUTO_NATIVE_LIBRARIES=0 there

sbc100 commented 1 year ago

Awesome! And great that you were able to get something working so quickly. I certainly recommend -sMAIN_MODULE=2 for that use case.. that way you control what the plugins have access to and you can expand that as needed.

Finally, a word or warning, the ABI used by the dyanmic linker could change in the future, and being backward compatible with older side modules (i.e. side modules built with previous versions of emscripten) is not something we are currently test. This is rather hard problem, as you might imagine, as there is basically no limit the amount of internal surface area that a side module can access or depend on.

Keeping you surface area limited to just the functions you pass in sounds like a great way to minimize the changes of breakages over time. That way, your old plugins would only break if we changed to core C ABI, or the core binary format of side modules themselves, which is something we would not do without a lot of consideration.

ericoporto commented 5 months ago

Is this issue still valid?

sbc100 commented 5 months ago

I think this can now closed.