Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ See docs/process.md for more on how version tagging works.
variables to the generated program, when running under Node. This setting is
enabled by default when `-sNODERAWFS` is used but can also be controlled
separately. (#18820)
- A new `-sFAKE_DYLIBS` setting was added. When enabled you get the current
emscripten behavior of the `-shared` flag, which is to produce regular object
files instead of actual shared shared libraries (side modules). Because this
setting is enabled by default this doesn't change the default behavior of the
compiler. If you want to experiment with real shared libraries you can
explicitly disable this setting. (#25826)

4.0.20 - 11/18/25
-----------------
Expand Down
13 changes: 13 additions & 0 deletions site/source/docs/tools_reference/settings_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3424,6 +3424,19 @@ indirectly using `importScripts`

Default value: false

.. _fake_dylibs:

FAKE_DYLIBS
===========

This setting changes the behaviour of the ``-shared`` flag. The default
setting of ``true`` means the ``-shared`` flag actually produces a normal
object file (i.e. ``ld -r``). Setting this to false will cause ``-shared``
to behave like :ref:`SIDE_MODULE` and produce and dynamically linked
library.

Default value: true

.. _deprecated-settings:

===================
Expand Down
7 changes: 7 additions & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -2240,3 +2240,10 @@ var WASM_JS_TYPES = false;
// CROSS_ORIGIN uses an inline worker to instead load the worker script
// indirectly using `importScripts`
var CROSS_ORIGIN = false;

// This setting changes the behaviour of the ``-shared`` flag. The default
// setting of ``true`` means the ``-shared`` flag actually produces a normal
// object file (i.e. ``ld -r``). Setting this to false will cause ``-shared``
// to behave like :ref:`SIDE_MODULE` and produce and dynamically linked
// library.
var FAKE_DYLIBS = true;
24 changes: 20 additions & 4 deletions test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -4033,19 +4033,35 @@ def dylink_testf(self, main, side=None, expected=None, force_c=False, main_cflag
old_settings = dict(self.settings_mods)
self.clear_setting('MODULARIZE')
self.clear_setting('MAIN_MODULE')
self.set_setting('SIDE_MODULE')
self.clear_setting('SIDE_MODULE')
so_file = os.path.join(so_dir, so_name)

# Using -shared + -sFAKE_DYLIBS should be the same as `-sSIDE_MODULE`
flags = ['-sSIDE_MODULE']
if isinstance(side, list):
# side is just a library
self.run_process([EMCC] + side + self.get_cflags() + ['-o', so_file])
self.run_process([EMCC] + side + self.get_cflags() + flags + ['-o', so_file])
else:
out_file = self.build(side, output_suffix='.so')
out_file = self.build(side, output_suffix='.so', cflags=flags)
shutil.move(out_file, so_file)

shutil.move(so_file, so_file + '.orig')

# Verify that building with -sSIDE_MODULE is essentailly the same as building with `-shared -fPIC -sFAKE_DYLIBS=0`.
flags = ['-shared', '-fPIC', '-sFAKE_DYLIBS=0']
if isinstance(side, list):
# side is just a library
self.run_process([EMCC] + side + self.get_cflags() + flags + ['-o', so_file])
else:
out_file = self.build(side, output_suffix='.so', cflags=flags)
shutil.move(out_file, so_file)

self.assertEqual(read_binary(so_file), read_binary(so_file + '.orig'))
os.remove(so_file + '.orig')

# main settings
self.settings_mods = old_settings
self.set_setting('MAIN_MODULE', main_module)
self.clear_setting('SIDE_MODULE')
self.cflags += main_cflags
self.cflags.append(so_file)

Expand Down
9 changes: 8 additions & 1 deletion test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -11919,14 +11919,21 @@ def test_euidaccess(self):
self.do_other_test('test_euidaccess.c')

def test_shared_flag(self):
self.run_process([EMCC, '-shared', test_file('hello_world.c'), '-o', 'libother.so'])
create_file('side.c', 'int foo;')
self.run_process([EMCC, '-shared', 'side.c', '-o', 'libother.so'])

# Test that `-shared` flag causes object file generation but gives a warning
err = self.run_process([EMCC, '-shared', test_file('hello_world.c'), '-o', 'out.foo', 'libother.so'], stderr=PIPE).stderr
self.assertContained('linking a library with `-shared` will emit a static object', err)
self.assertContained('emcc: warning: ignoring dynamic library libother.so when generating an object file, this will need to be included explicitly in the final link', err)
self.assertIsObjectFile('out.foo')

# Test that adding `-sFAKE_DYIBS=0` build a real side module
err = self.run_process([EMCC, '-shared', '-fPIC', '-sFAKE_DYLIBS=0', test_file('hello_world.c'), '-o', 'out.foo', 'libother.so'], stderr=PIPE).stderr
self.assertNotContained('linking a library with `-shared` will emit a static object', err)
self.assertNotContained('emcc: warning: ignoring dynamic library libother.so when generating an object file, this will need to be included explicitly in the final link', err)
self.assertIsWasmDylib('out.foo')

# Test that using an executable output name overrides the `-shared` flag, but produces a warning.
err = self.run_process([EMCC, '-shared', test_file('hello_world.c'), '-o', 'out.js'],
stderr=PIPE).stderr
Expand Down
1 change: 1 addition & 0 deletions tools/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class EmccOptions:
dash_M = False
dash_S = False
dash_c = False
dylibs: List[str] = []
embed_files: List[str] = []
emit_symbol_map = False
emit_tsd = ''
Expand Down
60 changes: 45 additions & 15 deletions tools/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,22 @@ def setup_sanitizers(options):
settings.LOAD_SOURCE_MAP = 1


def get_dylibs(options, linker_args):
"""Find all the Wasm dynanamic libraries specified on the command line,
either via `-lfoo` or via `libfoo.so` directly."""

dylibs = []
for arg in linker_args:
if arg.startswith('-l'):
for ext in DYLIB_EXTENSIONS:
path = find_library('lib' + arg[2:] + ext, options.lib_dirs)
if path and building.is_wasm_dylib(path):
dylibs.append(path)
elif building.is_wasm_dylib(arg):
dylibs.append(arg)
return dylibs


@ToolchainProfiler.profile_block('linker_setup')
def phase_linker_setup(options, linker_args): # noqa: C901, PLR0912, PLR0915
"""Future modifications should consider refactoring to reduce complexity.
Expand All @@ -843,6 +859,21 @@ def phase_linker_setup(options, linker_args): # noqa: C901, PLR0912, PLR0915
setup_environment_settings()

apply_library_settings(linker_args)

if settings.SIDE_MODULE or settings.MAIN_MODULE:
default_setting('FAKE_DYLIBS', 0)

if options.shared and not settings.FAKE_DYLIBS:
default_setting('SIDE_MODULE', 1)
default_setting('RELOCATABLE', 1)

if not settings.FAKE_DYLIBS:
options.dylibs = get_dylibs(options, linker_args)
# If there are any dynamically linked libraries on the command line then
# need to enable `MAIN_MODULE` in order to produce JS code that can load them.
if not settings.MAIN_MODULE and not settings.SIDE_MODULE and options.dylibs:
default_setting('MAIN_MODULE', 2)

linker_args += calc_extra_ldflags(options)

# We used to do this check during on startup during `check_sanity`, but
Expand Down Expand Up @@ -936,16 +967,14 @@ def phase_linker_setup(options, linker_args): # noqa: C901, PLR0912, PLR0915

# If no output format was specified we try to deduce the format based on
# the output filename extension
if not options.oformat and (options.relocatable or (options.shared and not settings.SIDE_MODULE)):
# Until we have a better story for actually producing runtime shared libraries
# we support a compatibility mode where shared libraries are actually just
# object files linked with `wasm-ld --relocatable` or `llvm-link` in the case
# of LTO.
if not options.oformat and (options.relocatable or (options.shared and settings.FAKE_DYLIBS and not settings.SIDE_MODULE)):
# With FAKE_DYLIBS we generate an normal object file rather than an shared object.
# This is linked with `wasm-ld --relocatable` or (`llvm-link` in the case of LTO).
if final_suffix in EXECUTABLE_EXTENSIONS:
diagnostics.warning('emcc', '-shared/-r used with executable output suffix. This behaviour is deprecated. Please remove -shared/-r to build an executable or avoid the executable suffix (%s) when building object files.' % final_suffix)
else:
if options.shared:
diagnostics.warning('emcc', 'linking a library with `-shared` will emit a static object file. This is a form of emulation to support existing build systems. If you want to build a runtime shared library use the SIDE_MODULE setting.')
if options.shared and 'FAKE_DYLIBS' not in user_settings:
diagnostics.warning('emcc', 'linking a library with `-shared` will emit a static object file (FAKE_DYLIBS defaults to true). If you want to build a runtime shared library use the SIDE_MODULE or FAKE_DYLIBS=0.')
options.oformat = OFormat.OBJECT

if not options.oformat:
Expand Down Expand Up @@ -2730,10 +2759,10 @@ def map_to_js_libs(library_name):


def process_libraries(options, flags):
"""Process `-l` and `--js-library` flags."""
new_flags = []
system_libs_map = system_libs.Library.get_usable_variations()

# Process `-l` and `--js-library` flags
for flag in flags:
if flag.startswith('--js-library='):
js_lib = flag.split('=', 1)[1]
Expand Down Expand Up @@ -2854,7 +2883,7 @@ def replacement(self):


def filter_out_fake_dynamic_libs(options, inputs):
# Filters out "fake" dynamic libraries that are really just intermediate object files.
"""Filter out "fake" dynamic libraries that are really just intermediate object files."""
def is_fake_dylib(input_file):
if get_file_suffix(input_file) in DYLIB_EXTENSIONS and os.path.exists(input_file) and not building.is_wasm_dylib(input_file):
if not options.ignore_dynamic_linking:
Expand All @@ -2866,11 +2895,13 @@ def is_fake_dylib(input_file):
return [f for f in inputs if not is_fake_dylib(f)]


def filter_out_duplicate_dynamic_libs(inputs):
def filter_out_duplicate_fake_dynamic_libs(inputs):
"""Filter out duplicate "fake" shared libraries (intermediate object files).

See test_core.py:test_redundant_link
"""
seen = set()

# Filter out duplicate "fake" shared libraries (intermediate object files).
# See test_core.py:test_redundant_link
def check(input_file):
if get_file_suffix(input_file) in DYLIB_EXTENSIONS and not building.is_wasm_dylib(input_file):
abspath = os.path.abspath(input_file)
Expand Down Expand Up @@ -3086,11 +3117,10 @@ def phase_calculate_linker_inputs(options, linker_args):
if options.oformat == OFormat.OBJECT or options.ignore_dynamic_linking:
linker_args = filter_out_fake_dynamic_libs(options, linker_args)
else:
linker_args = filter_out_duplicate_dynamic_libs(linker_args)
linker_args = filter_out_duplicate_fake_dynamic_libs(linker_args)

if settings.MAIN_MODULE:
dylibs = [a for a in linker_args if building.is_wasm_dylib(a)]
process_dynamic_libs(dylibs, options.lib_dirs)
process_dynamic_libs(options.dylibs, options.lib_dirs)

return linker_args

Expand Down