Skip to content

Commit a832b28

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 91fefbf commit a832b28

File tree

7 files changed

+96
-20
lines changed

7 files changed

+96
-20
lines changed

ChangeLog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ See docs/process.md for more on how version tagging works.
2020

2121
4.0.21 (in development)
2222
-----------------------
23+
- A new `-sFAKE_DYLIBS` setting was added. This is enabled by default but can
24+
be disabled order to change the behaviour of the ``-shared`` flag to produce
25+
actual shared libraries (side modules).
2326

2427
4.0.20 - 11/18/25
2528
-----------------

site/source/docs/tools_reference/settings_reference.rst

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

34163416
Default value: false
34173417

3418+
.. _fake_dylibs:
3419+
3420+
FAKE_DYLIBS
3421+
===========
3422+
3423+
This settings changes the behaviour of the ``-shared`` flag. They default
3424+
setting of ``true`` means the ``-shared`` flag actually produces a normal
3425+
object file (i.e. ``ld -r``). Setting this to false will cause ``-shared``
3426+
to behavour like :ref:`SIDE_MODULE` and produce and dynamically linked
3427+
library.
3428+
3429+
Default value: true
3430+
34183431
.. _deprecated-settings:
34193432

34203433
===================

src/settings.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2230,3 +2230,10 @@ var WASM_JS_TYPES = false;
22302230
// CROSS_ORIGIN uses an inline worker to instead load the worker script
22312231
// indirectly using `importScripts`
22322232
var CROSS_ORIGIN = false;
2233+
2234+
// This settings changes the behaviour of the ``-shared`` flag. They default
2235+
// setting of ``true`` means the ``-shared`` flag actually produces a normal
2236+
// object file (i.e. ``ld -r``). Setting this to false will cause ``-shared``
2237+
// to behavour like :ref:`SIDE_MODULE` and produce and dynamically linked
2238+
// library.
2239+
var FAKE_DYLIBS = true;

test/test_core.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4127,19 +4127,34 @@ def dylink_testf(self, main, side=None, expected=None, force_c=False, main_cflag
41274127
old_settings = dict(self.settings_mods)
41284128
self.clear_setting('MODULARIZE')
41294129
self.clear_setting('MAIN_MODULE')
4130-
self.set_setting('SIDE_MODULE')
4130+
self.clear_setting('SIDE_MODULE')
41314131
so_file = os.path.join(so_dir, so_name)
4132+
4133+
# Using -shared + -sFAKE_DYLIBS should be the same as `-sSIDE_MODULE`
4134+
flags = ['-sSIDE_MODULE']
41324135
if isinstance(side, list):
41334136
# side is just a library
4134-
self.run_process([EMCC] + side + self.get_cflags() + ['-o', so_file])
4137+
self.run_process([EMCC] + side + self.get_cflags() + flags + ['-o', so_file])
41354138
else:
4136-
out_file = self.build(side, output_suffix='.so')
4139+
out_file = self.build(side, output_suffix='.so', cflags=flags)
41374140
shutil.move(out_file, so_file)
41384141

4142+
# Verify that building with -sSIDE_MODULE is essentailly the same as building with `-shared -fPIC -sFAKE_DYLIBS=0`.
4143+
flags = ['-shared', '-fPIC', '-sFAKE_DYLIBS=0']
4144+
shared_file = so_file + '.shared'
4145+
if isinstance(side, list):
4146+
# side is just a library
4147+
self.run_process([EMCC] + side + self.get_cflags() + flags + ['-o', shared_file])
4148+
else:
4149+
out_file = self.build(side, output_basename='shared', output_suffix='.so', cflags=flags)
4150+
shutil.move(out_file, shared_file)
4151+
4152+
self.assertTrue(read_binary(so_file), read_binary(shared_file))
4153+
os.remove(shared_file)
4154+
41394155
# main settings
41404156
self.settings_mods = old_settings
41414157
self.set_setting('MAIN_MODULE', main_module)
4142-
self.clear_setting('SIDE_MODULE')
41434158
self.cflags += main_cflags
41444159
self.cflags.append(so_file)
41454160

test/test_other.py

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

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

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

11916+
# Test that adding `-sFAKE_DYIBS` build a real side module
11917+
err = self.run_process([EMCC, '-shared', '-fPIC', '-sFAKE_DYLIBS=0', test_file('hello_world.c'), '-o', 'out.foo', 'libother.so'], stderr=PIPE).stderr
11918+
self.assertNotContained('linking a library with `-shared` will emit a static object', err)
11919+
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)
11920+
self.assertIsWasmDylib('out.foo')
11921+
1191511922
# Test that using an executable output name overrides the `-shared` flag, but produces a warning.
1191611923
err = self.run_process([EMCC, '-shared', test_file('hello_world.c'), '-o', 'out.js'],
1191711924
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 vis `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 there are any dynamically linked libraries on the command line then
871+
# need to enable `MAIN_MODULE` in order to produce JS code that can load them.
872+
if not settings.FAKE_DYLIBS:
873+
options.dylibs = get_dylibs(options, linker_args)
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:
@@ -2752,10 +2781,10 @@ def map_to_js_libs(library_name):
27522781

27532782

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

2758-
# Process `-l` and `--js-library` flags
27592788
for flag in flags:
27602789
if flag.startswith('--js-library='):
27612790
js_lib = flag.split('=', 1)[1]
@@ -2876,7 +2905,7 @@ def replacement(self):
28762905

28772906

28782907
def filter_out_fake_dynamic_libs(options, inputs):
2879-
# Filters out "fake" dynamic libraries that are really just intermediate object files.
2908+
"""Filter out "fake" dynamic libraries that are really just intermediate object files."""
28802909
def is_fake_dylib(input_file):
28812910
if get_file_suffix(input_file) in DYLIB_EXTENSIONS and os.path.exists(input_file) and not building.is_wasm_dylib(input_file):
28822911
if not options.ignore_dynamic_linking:
@@ -2888,11 +2917,13 @@ def is_fake_dylib(input_file):
28882917
return [f for f in inputs if not is_fake_dylib(f)]
28892918

28902919

2891-
def filter_out_duplicate_dynamic_libs(inputs):
2920+
def filter_out_duplicate_fake_dynamic_libs(inputs):
2921+
"""Filter out duplicate "fake" shared libraries (intermediate object files).
2922+
2923+
See test_core.py:test_redundant_link
2924+
"""
28922925
seen = set()
28932926

2894-
# Filter out duplicate "fake" shared libraries (intermediate object files).
2895-
# See test_core.py:test_redundant_link
28962927
def check(input_file):
28972928
if get_file_suffix(input_file) in DYLIB_EXTENSIONS and not building.is_wasm_dylib(input_file):
28982929
abspath = os.path.abspath(input_file)
@@ -3108,11 +3139,10 @@ def phase_calculate_linker_inputs(options, linker_args):
31083139
if options.oformat == OFormat.OBJECT or options.ignore_dynamic_linking:
31093140
linker_args = filter_out_fake_dynamic_libs(options, linker_args)
31103141
else:
3111-
linker_args = filter_out_duplicate_dynamic_libs(linker_args)
3142+
linker_args = filter_out_duplicate_fake_dynamic_libs(linker_args)
31123143

31133144
if settings.MAIN_MODULE:
3114-
dylibs = [a for a in linker_args if building.is_wasm_dylib(a)]
3115-
process_dynamic_libs(dylibs, options.lib_dirs)
3145+
process_dynamic_libs(options.dylibs, options.lib_dirs)
31163146

31173147
return linker_args
31183148

0 commit comments

Comments
 (0)