Skip to content

Commit bda7f13

Browse files
committed
Enable -shared via a new FAKE_DYLIBS setting. NFC
This this change the default behaviour does not change since `FAKE_DYLIBS` defaults to false. However for users who want to try out a more traditional shared library workflow `-sFAKE_DYLIBS=0` can be used enable shared library output with `-shared`. At some point in the future we can consider making this the default. Split out from emscripten-core#25817
1 parent 20ab3d4 commit bda7f13

File tree

7 files changed

+100
-20
lines changed

7 files changed

+100
-20
lines changed

ChangeLog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ See docs/process.md for more on how version tagging works.
3030
variables to the generated program, when running under Node. This setting is
3131
enabled by default when `-sNODERAWFS` is used but can also be controlled
3232
separately. (#18820)
33+
- A new `-sFAKE_DYLIBS` setting was added. When enabled you get the current
34+
emscripten behavior of the `-shared` flag, which is to produce regular object
35+
files instead of actual shared shared libraries (side modules). Because this
36+
setting is enabled by default this doesn't change the default behavior of the
37+
compiler. If you want to experiment with real shared libraries you can
38+
explicitly disable this setting. (#25826)
3339

3440
4.0.20 - 11/18/25
3541
-----------------

site/source/docs/tools_reference/settings_reference.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3424,6 +3424,19 @@ indirectly using `importScripts`
34243424

34253425
Default value: false
34263426

3427+
.. _fake_dylibs:
3428+
3429+
FAKE_DYLIBS
3430+
===========
3431+
3432+
This setting changes the behaviour of the ``-shared`` flag. The default
3433+
setting of ``true`` means the ``-shared`` flag actually produces a normal
3434+
object file (i.e. ``ld -r``). Setting this to false will cause ``-shared``
3435+
to behave like :ref:`SIDE_MODULE` and produce and dynamically linked
3436+
library.
3437+
3438+
Default value: true
3439+
34273440
.. _deprecated-settings:
34283441

34293442
===================

src/settings.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2240,3 +2240,10 @@ var WASM_JS_TYPES = false;
22402240
// CROSS_ORIGIN uses an inline worker to instead load the worker script
22412241
// indirectly using `importScripts`
22422242
var CROSS_ORIGIN = false;
2243+
2244+
// This setting changes the behaviour of the ``-shared`` flag. The default
2245+
// setting of ``true`` means the ``-shared`` flag actually produces a normal
2246+
// object file (i.e. ``ld -r``). Setting this to false will cause ``-shared``
2247+
// to behave like :ref:`SIDE_MODULE` and produce and dynamically linked
2248+
// library.
2249+
var FAKE_DYLIBS = true;

test/test_core.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4033,19 +4033,35 @@ def dylink_testf(self, main, side=None, expected=None, force_c=False, main_cflag
40334033
old_settings = dict(self.settings_mods)
40344034
self.clear_setting('MODULARIZE')
40354035
self.clear_setting('MAIN_MODULE')
4036-
self.set_setting('SIDE_MODULE')
4036+
self.clear_setting('SIDE_MODULE')
40374037
so_file = os.path.join(so_dir, so_name)
4038+
4039+
# Using -shared + -sFAKE_DYLIBS should be the same as `-sSIDE_MODULE`
4040+
flags = ['-sSIDE_MODULE']
40384041
if isinstance(side, list):
40394042
# side is just a library
4040-
self.run_process([EMCC] + side + self.get_cflags() + ['-o', so_file])
4043+
self.run_process([EMCC] + side + self.get_cflags() + flags + ['-o', so_file])
40414044
else:
4042-
out_file = self.build(side, output_suffix='.so')
4045+
out_file = self.build(side, output_suffix='.so', cflags=flags)
40434046
shutil.move(out_file, so_file)
40444047

4048+
shutil.move(so_file, so_file + '.orig')
4049+
4050+
# Verify that building with -sSIDE_MODULE is essentailly the same as building with `-shared -fPIC -sFAKE_DYLIBS=0`.
4051+
flags = ['-shared', '-fPIC', '-sFAKE_DYLIBS=0']
4052+
if isinstance(side, list):
4053+
# side is just a library
4054+
self.run_process([EMCC] + side + self.get_cflags() + flags + ['-o', so_file])
4055+
else:
4056+
out_file = self.build(side, output_suffix='.so', cflags=flags)
4057+
shutil.move(out_file, so_file)
4058+
4059+
self.assertEqual(read_binary(so_file), read_binary(so_file + '.orig'))
4060+
os.remove(so_file + '.orig')
4061+
40454062
# main settings
40464063
self.settings_mods = old_settings
40474064
self.set_setting('MAIN_MODULE', main_module)
4048-
self.clear_setting('SIDE_MODULE')
40494065
self.cflags += main_cflags
40504066
self.cflags.append(so_file)
40514067

test/test_other.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11919,14 +11919,21 @@ def test_euidaccess(self):
1191911919
self.do_other_test('test_euidaccess.c')
1192011920

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

1192411925
# Test that `-shared` flag causes object file generation but gives a warning
1192511926
err = self.run_process([EMCC, '-shared', test_file('hello_world.c'), '-o', 'out.foo', 'libother.so'], stderr=PIPE).stderr
1192611927
self.assertContained('linking a library with `-shared` will emit a static object', err)
1192711928
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)
1192811929
self.assertIsObjectFile('out.foo')
1192911930

11931+
# Test that adding `-sFAKE_DYIBS=0` build a real side module
11932+
err = self.run_process([EMCC, '-shared', '-fPIC', '-sFAKE_DYLIBS=0', test_file('hello_world.c'), '-o', 'out.foo', 'libother.so'], stderr=PIPE).stderr
11933+
self.assertNotContained('linking a library with `-shared` will emit a static object', err)
11934+
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)
11935+
self.assertIsWasmDylib('out.foo')
11936+
1193011937
# Test that using an executable output name overrides the `-shared` flag, but produces a warning.
1193111938
err = self.run_process([EMCC, '-shared', test_file('hello_world.c'), '-o', 'out.js'],
1193211939
stderr=PIPE).stderr

tools/cmdline.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class EmccOptions:
6565
dash_M = False
6666
dash_S = False
6767
dash_c = False
68+
dylibs: List[str] = []
6869
embed_files: List[str] = []
6970
emit_symbol_map = False
7071
emit_tsd = ''

tools/link.py

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,22 @@ def setup_sanitizers(options):
829829
settings.LOAD_SOURCE_MAP = 1
830830

831831

832+
def get_dylibs(options, linker_args):
833+
"""Find all the Wasm dynanamic libraries specified on the command line,
834+
either via `-lfoo` or via `libfoo.so` directly."""
835+
836+
dylibs = []
837+
for arg in linker_args:
838+
if arg.startswith('-l'):
839+
for ext in DYLIB_EXTENSIONS:
840+
path = find_library('lib' + arg[2:] + ext, options.lib_dirs)
841+
if path and building.is_wasm_dylib(path):
842+
dylibs.append(path)
843+
elif building.is_wasm_dylib(arg):
844+
dylibs.append(arg)
845+
return dylibs
846+
847+
832848
@ToolchainProfiler.profile_block('linker_setup')
833849
def phase_linker_setup(options, linker_args): # noqa: C901, PLR0912, PLR0915
834850
"""Future modifications should consider refactoring to reduce complexity.
@@ -843,6 +859,21 @@ def phase_linker_setup(options, linker_args): # noqa: C901, PLR0912, PLR0915
843859
setup_environment_settings()
844860

845861
apply_library_settings(linker_args)
862+
863+
if settings.SIDE_MODULE or settings.MAIN_MODULE:
864+
default_setting('FAKE_DYLIBS', 0)
865+
866+
if options.shared and not settings.FAKE_DYLIBS:
867+
default_setting('SIDE_MODULE', 1)
868+
default_setting('RELOCATABLE', 1)
869+
870+
if not settings.FAKE_DYLIBS:
871+
options.dylibs = get_dylibs(options, linker_args)
872+
# If there are any dynamically linked libraries on the command line then
873+
# need to enable `MAIN_MODULE` in order to produce JS code that can load them.
874+
if not settings.MAIN_MODULE and not settings.SIDE_MODULE and options.dylibs:
875+
default_setting('MAIN_MODULE', 2)
876+
846877
linker_args += calc_extra_ldflags(options)
847878

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

937968
# If no output format was specified we try to deduce the format based on
938969
# the output filename extension
939-
if not options.oformat and (options.relocatable or (options.shared and not settings.SIDE_MODULE)):
940-
# Until we have a better story for actually producing runtime shared libraries
941-
# we support a compatibility mode where shared libraries are actually just
942-
# object files linked with `wasm-ld --relocatable` or `llvm-link` in the case
943-
# of LTO.
970+
if not options.oformat and (options.relocatable or (options.shared and settings.FAKE_DYLIBS and not settings.SIDE_MODULE)):
971+
# With FAKE_DYLIBS we generate an normal object file rather than an shared object.
972+
# This is linked with `wasm-ld --relocatable` or (`llvm-link` in the case of LTO).
944973
if final_suffix in EXECUTABLE_EXTENSIONS:
945974
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)
946975
else:
947-
if options.shared:
948-
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.')
976+
if options.shared and 'FAKE_DYLIBS' not in user_settings:
977+
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.')
949978
options.oformat = OFormat.OBJECT
950979

951980
if not options.oformat:
@@ -2730,10 +2759,10 @@ def map_to_js_libs(library_name):
27302759

27312760

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

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

28552884

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

28682897

2869-
def filter_out_duplicate_dynamic_libs(inputs):
2898+
def filter_out_duplicate_fake_dynamic_libs(inputs):
2899+
"""Filter out duplicate "fake" shared libraries (intermediate object files).
2900+
2901+
See test_core.py:test_redundant_link
2902+
"""
28702903
seen = set()
28712904

2872-
# Filter out duplicate "fake" shared libraries (intermediate object files).
2873-
# See test_core.py:test_redundant_link
28742905
def check(input_file):
28752906
if get_file_suffix(input_file) in DYLIB_EXTENSIONS and not building.is_wasm_dylib(input_file):
28762907
abspath = os.path.abspath(input_file)
@@ -3086,11 +3117,10 @@ def phase_calculate_linker_inputs(options, linker_args):
30863117
if options.oformat == OFormat.OBJECT or options.ignore_dynamic_linking:
30873118
linker_args = filter_out_fake_dynamic_libs(options, linker_args)
30883119
else:
3089-
linker_args = filter_out_duplicate_dynamic_libs(linker_args)
3120+
linker_args = filter_out_duplicate_fake_dynamic_libs(linker_args)
30903121

30913122
if settings.MAIN_MODULE:
3092-
dylibs = [a for a in linker_args if building.is_wasm_dylib(a)]
3093-
process_dynamic_libs(dylibs, options.lib_dirs)
3123+
process_dynamic_libs(options.dylibs, options.lib_dirs)
30943124

30953125
return linker_args
30963126

0 commit comments

Comments
 (0)