Skip to content

Commit 9fcb66c

Browse files
committed
Enable real dynamic linking by default
This change essentially disables `FAKE_DYLIBS` by default, which in turn means `-shared` will now produce dynamic libraries by default. If you want the old behaviour you now need `-sFAKE_DYLIBS`.
1 parent d0d2881 commit 9fcb66c

8 files changed

Lines changed: 52 additions & 76 deletions

File tree

ChangeLog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ See docs/process.md for more on how version tagging works.
4343
notified of all IDBFS sync start and end events. (#26895)
4444
- google-closure-compiler was updated to 20260429.0.0. (#26869)
4545
Closure compiler now provides a native macOS arm64 binary for Apple Silicon.
46+
- The FAKE_DYLIBS setting is now disabled by default. This means that `-shared`
47+
will now produce real dynamic libraries by default. It also means that
48+
`-sMAIN_MODULE=2` is not implicit if any real dynamic libraries are included
49+
in the link. (#25930)
4650

4751
5.0.7 - 04/30/26
4852
----------------

cmake/Modules/Platform/Emscripten.cmake

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ set(CMAKE_SYSTEM_NAME Emscripten)
1818
set(CMAKE_SYSTEM_VERSION 1)
1919

2020
set(CMAKE_CROSSCOMPILING TRUE)
21-
set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS FALSE)
2221

2322
# Advertise Emscripten as a 32-bit platform (as opposed to
2423
# CMAKE_SYSTEM_PROCESSOR=x86_64 for 64-bit platform), since some projects (e.g.

site/source/docs/tools_reference/settings_reference.rst

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3354,13 +3354,12 @@ Default value: false
33543354
FAKE_DYLIBS
33553355
===========
33563356

3357-
This setting changes the behaviour of the ``-shared`` flag. The default
3358-
setting of ``true`` means the ``-shared`` flag actually produces a normal
3359-
object file (i.e. ``ld -r``). Setting this to false will cause ``-shared``
3360-
to behave like :ref:`SIDE_MODULE` and produce a dynamically linked
3361-
library.
3357+
This setting changes the behaviour of the ``-shared`` flag. When set to true
3358+
you get the old emscripten behaivour where the ``-shared`` flag actually
3359+
produces a normal object file (i.e. ``ld -r``). The default behaviour is that
3360+
`-shared` is the as :ref:`SIDE_MODULE`.
33623361

3363-
Default value: true
3362+
Default value: false
33643363

33653364
.. _executable:
33663365

src/settings.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,12 +2199,11 @@ var GROWABLE_ARRAYBUFFERS = false;
21992199
// indirectly using `importScripts`
22002200
var CROSS_ORIGIN = false;
22012201

2202-
// This setting changes the behaviour of the ``-shared`` flag. The default
2203-
// setting of ``true`` means the ``-shared`` flag actually produces a normal
2204-
// object file (i.e. ``ld -r``). Setting this to false will cause ``-shared``
2205-
// to behave like :ref:`SIDE_MODULE` and produce a dynamically linked
2206-
// library.
2207-
var FAKE_DYLIBS = true;
2202+
// This setting changes the behaviour of the ``-shared`` flag. When set to true
2203+
// you get the old emscripten behaivour where the ``-shared`` flag actually
2204+
// produces a normal object file (i.e. ``ld -r``). The default behaviour is that
2205+
// `-shared` is the as :ref:`SIDE_MODULE`.
2206+
var FAKE_DYLIBS = false;
22082207

22092208
// Add a #! line to generated JS file and make it executable. This is useful
22102209
// for building command line tools that run under node.

test/test_core.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4059,8 +4059,8 @@ def dylink_testf(self, main, side=None, expected=None, force_c=False, main_cflag
40594059

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

4062-
# Verify that building with -sSIDE_MODULE is essentially the same as building with `-shared -fPIC -sFAKE_DYLIBS=0`.
4063-
flags = ['-shared', '-fPIC', '-sFAKE_DYLIBS=0']
4062+
# Verify that building with -sSIDE_MODULE is essentially the same as building with `-shared -fPIC`
4063+
flags = ['-shared', '-fPIC']
40644064
if isinstance(side, list):
40654065
# side is just a library
40664066
self.run_process([EMCC] + side + self.get_cflags() + flags + ['-o', so_file])

test/test_other.py

Lines changed: 25 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,7 +1173,7 @@ def test_odd_suffixes(self):
11731173
for suffix in ('lo',):
11741174
self.clear()
11751175
print(suffix)
1176-
self.run_process([EMCC, test_file('hello_world.c'), '-shared', '-o', 'binary.' + suffix])
1176+
self.run_process([EMCC, test_file('hello_world.c'), '-sFAKE_DYLIBS', '-shared', '-o', 'binary.' + suffix])
11771177
self.run_process([EMCC, 'binary.' + suffix])
11781178
self.assertContained('Hello, world!', self.run_js('a.out.js'))
11791179

@@ -1354,7 +1354,7 @@ def test_multiply_defined_libsymbols(self):
13541354
''')
13551355

13561356
self.cflags.remove('-Werror')
1357-
self.emcc('libA.c', ['-shared', '-o', 'libA.so'])
1357+
self.emcc('libA.c', ['-shared', '-sFAKE_DYLIBS', '-o', 'libA.so'])
13581358

13591359
self.emcc('a2.c', ['-r', '-L.', '-lA', '-o', 'a2.o'])
13601360
self.emcc('b2.c', ['-r', '-L.', '-lA', '-o', 'b2.o'])
@@ -1487,7 +1487,12 @@ def test_link_group_bitcode(self):
14871487
# We deliberately ignore duplicate input files in order to allow
14881488
# "libA.so" on the command line twice. This is not really .so support
14891489
# and the .so files are really object files.
1490-
def test_redundant_link(self):
1490+
@parameterized({
1491+
'': ([],),
1492+
'fake_dylibs': (['-sFAKE_DYLIBS'],),
1493+
})
1494+
def test_redundant_link(self, args):
1495+
self.cflags += args
14911496
create_file('libA.c', 'int mult() { return 1; }')
14921497
create_file('main.c', r'''
14931498
#include <stdio.h>
@@ -1499,7 +1504,7 @@ def test_redundant_link(self):
14991504
''')
15001505

15011506
self.cflags.remove('-Werror')
1502-
self.emcc('libA.c', ['-shared', '-o', 'libA.so'])
1507+
self.emcc('libA.c', ['-fPIC', '-shared', '-o', 'libA.so'])
15031508
self.emcc('main.c', ['libA.so', 'libA.so', '-o', 'a.out.js'])
15041509
self.assertContained('result: 1', self.run_js('a.out.js'))
15051510

@@ -2147,9 +2152,9 @@ def test_multidynamic_link(self, link_flags, lib_suffix):
21472152
''')
21482153

21492154
# Build libfile normally into an .so
2150-
self.run_process([EMCC, 'libdir/libfile.c', '-shared', '-o', 'libdir/libfile.so' + lib_suffix])
2155+
self.run_process([EMCC, 'libdir/libfile.c', '-sFAKE_DYLIBS', '-shared', '-fPIC', '-o', 'libdir/libfile.so' + lib_suffix])
21512156
# Build libother and dynamically link it to libfile
2152-
self.run_process([EMCC, '-Llibdir', 'libdir/libother.c'] + link_flags + ['-shared', '-o', 'libdir/libother.so'])
2157+
self.run_process([EMCC, '-Llibdir', 'libdir/libother.c'] + link_flags + ['-sFAKE_DYLIBS', '-shared', '-fPIC', '-o', 'libdir/libother.so'])
21532158
# Build the main file, linking in both the libs
21542159
self.run_process([EMCC, '-Llibdir', os.path.join('main.c')] + link_flags + ['-lother', '-c'])
21552160
print('...')
@@ -4940,20 +4945,6 @@ def test_valid_abspath_2(self):
49404945
self.run_process(cmd)
49414946
self.assertContained('Hello, world!', self.run_js('a.out.js'))
49424947

4943-
def test_warn_dylibs(self):
4944-
shared_suffixes = ['.so', '.dylib', '.dll']
4945-
4946-
for suffix in ('.o', '.bc', '.so', '.dylib', '.js', '.html'):
4947-
print(suffix)
4948-
cmd = [EMCC, test_file('hello_world.c'), '-o', 'out' + suffix]
4949-
if suffix in {'.o', '.bc'}:
4950-
cmd.append('-c')
4951-
if suffix in {'.dylib', '.so'}:
4952-
cmd.append('-shared')
4953-
err = self.run_process(cmd, stderr=PIPE).stderr
4954-
warning = 'linking a library with `-shared` will emit a static object file'
4955-
self.assertContainedIf(warning, err, suffix in shared_suffixes)
4956-
49574948
@crossplatform
49584949
@parameterized({
49594950
'O2': [['-O2']],
@@ -8446,19 +8437,6 @@ def test_side_module_folder_deps(self):
84468437
self.run_process([EMCC, test_file('hello_world.c'), '-sSIDE_MODULE', '-o', 'subdir/libside2.so', '-L', 'subdir', '-lside1'])
84478438
self.run_process([EMCC, test_file('hello_world.c'), '-sMAIN_MODULE', '-o', 'main.js', '-L', 'subdir', '-lside2'])
84488439

8449-
@crossplatform
8450-
def test_side_module_ignore(self):
8451-
self.run_process([EMCC, test_file('hello_world.c'), '-sSIDE_MODULE', '-o', 'libside.so'])
8452-
8453-
# Attempting to link statically against a side module (libside.so) should fail.
8454-
self.assert_fail([EMCC, '-L.', '-lside'], 'wasm-ld: error: unable to find library -lside')
8455-
8456-
# But a static library in the same location (libside.a) should take precedence.
8457-
self.run_process([EMCC, test_file('hello_world.c'), '-c'])
8458-
self.run_process([EMAR, 'cr', 'libside.a', 'hello_world.o'])
8459-
self.run_process([EMCC, '-L.', '-lside'])
8460-
self.assertContained('Hello, world!', self.run_js('a.out.js'))
8461-
84628440
@is_slow_test
84638441
@parameterized({
84648442
'': ([],),
@@ -12114,42 +12092,40 @@ def test_err(self):
1211412092
def test_euidaccess(self):
1211512093
self.do_other_test('test_euidaccess.c')
1211612094

12117-
def test_shared_flag(self):
12118-
create_file('side.c', 'int foo;')
12119-
self.run_process([EMCC, '-shared', 'side.c', '-o', 'libother.so'])
12095+
def test_fake_dylibs(self):
12096+
create_file('other.c', 'int foo = 10;')
12097+
self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-fPIC', 'other.c', '-o', 'libother.so'])
12098+
self.assertIsObjectFile('libother.so')
1212012099

12121-
# Test that `-shared` flag causes object file generation but gives a warning
12122-
err = self.run_process([EMCC, '-shared', test_file('hello_world.c'), '-o', 'out.foo', 'libother.so'], stderr=PIPE).stderr
12123-
self.assertContained('linking a library with `-shared` will emit a static object', err)
12100+
# Test that `-sFAKE_DYLIBS` flag causes object file generation and will generate a warning about
12101+
# dylink dependencies being ignored.
12102+
err = self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-fPIC', test_file('hello_world.c'), '-o', 'out.foo', 'libother.so'], stderr=PIPE).stderr
1212412103
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)
1212512104
self.assertIsObjectFile('out.foo')
1212612105

1212712106
# Test that adding `-sFAKE_DYLIBS=0` build a real side module
1212812107
err = self.run_process([EMCC, '-shared', '-fPIC', '-sFAKE_DYLIBS=0', test_file('hello_world.c'), '-o', 'out.foo', 'libother.so'], stderr=PIPE).stderr
12129-
self.assertNotContained('linking a library with `-shared` will emit a static object', err)
1213012108
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)
1213112109
self.assertIsWasmDylib('out.foo')
1213212110

1213312111
# Test that using an executable output name overrides the `-shared` flag, but produces a warning.
12134-
err = self.run_process([EMCC, '-shared', test_file('hello_world.c'), '-o', 'out.js'],
12112+
err = self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-fPIC', test_file('hello_world.c'), '-o', 'out.js'],
1213512113
stderr=PIPE).stderr
1213612114
self.assertContained('warning: -shared/-r used with executable output suffix', err)
1213712115
self.run_js('out.js')
1213812116

1213912117
def test_shared_soname(self):
12140-
self.run_process([EMCC, '-shared', '-Wl,-soname', '-Wl,libfoo.so.13', test_file('hello_world.c'), '-lc', '-o', 'libfoo.so'])
12118+
self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-Wl,-soname', '-Wl,libfoo.so.13', test_file('hello_world.c'), '-lc', '-o', 'libfoo.so'])
1214112119
self.run_process([EMCC, '-sSTRICT', 'libfoo.so'])
1214212120
self.assertContained('Hello, world!', self.run_js('a.out.js'))
1214312121

12144-
def test_shared_and_side_module_flag(self):
12145-
# Test that `-shared` and `-sSIDE_MODULE` flag causes wasm dylib generation without a warning.
12146-
err = self.run_process([EMCC, '-shared', '-sSIDE_MODULE', test_file('hello_world.c'), '-o', 'out.foo'], stderr=PIPE).stderr
12147-
self.assertNotContained('linking a library with `-shared` will emit a static object', err)
12122+
def test_shared_flag(self):
12123+
# Test that `-shared` flag causes wasm dylib generation
12124+
self.run_process([EMCC, '-shared', '-fPIC', test_file('hello_world.c'), '-o', 'out.foo'])
1214812125
self.assertIsWasmDylib('out.foo')
1214912126

12150-
# Test that `-shared` and `-sSIDE_MODULE` flag causes wasm dylib generation without a warning even if given executable output name.
12151-
err = self.run_process([EMCC, '-shared', '-sSIDE_MODULE', test_file('hello_world.c'), '-o', 'out.wasm'],
12152-
stderr=PIPE).stderr
12127+
# Test that `-shared` causes wasm dylib generation warning even if given executable output name.
12128+
err = self.run_process([EMCC, '-shared', '-fPIC', test_file('hello_world.c'), '-o', 'out.wasm'], stderr=PIPE).stderr
1215312129
self.assertNotContained('warning: -shared/-r used with executable output suffix', err)
1215412130
self.assertIsWasmDylib('out.wasm')
1215512131

tools/building.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ def lld_flags_for_executable(external_symbols):
165165
stub = create_stub_object(external_symbols)
166166
cmd.append(stub)
167167

168+
if settings.FAKE_DYLIBS:
169+
cmd.append('-Bstatic')
170+
else:
171+
# wasm-ld still default to static linking by default. If that ever changes we can remove this line.
172+
cmd.append('-Bdynamic')
173+
168174
if not settings.ERROR_ON_UNDEFINED_SYMBOLS:
169175
cmd.append('--import-undefined')
170176

@@ -203,7 +209,6 @@ def lld_flags_for_executable(external_symbols):
203209
c_exports = [e for e in c_exports if e not in external_symbols]
204210
c_exports += settings.REQUIRED_EXPORTS
205211
if settings.MAIN_MODULE:
206-
cmd.append('-Bdynamic')
207212
c_exports += side_module_external_deps(external_symbols)
208213
for export in c_exports:
209214
if settings.ERROR_ON_UNDEFINED_SYMBOLS:

tools/link.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -805,17 +805,14 @@ def phase_linker_setup(options, linker_args): # noqa: C901, PLR0912, PLR0915
805805

806806
apply_library_settings(linker_args)
807807

808-
if settings.SIDE_MODULE or settings.MAIN_MODULE:
809-
default_setting('FAKE_DYLIBS', 0)
810-
811808
if options.shared and not settings.FAKE_DYLIBS:
812809
default_setting('SIDE_MODULE', 1)
813810

814-
if not settings.FAKE_DYLIBS:
811+
if not settings.SIDE_MODULE and not settings.FAKE_DYLIBS:
815812
options.dylibs = get_dylibs(options, linker_args)
816-
# If there are any dynamically linked libraries on the command line then
817-
# need to enable `MAIN_MODULE` in order to produce JS code that can load them.
818-
if not settings.MAIN_MODULE and not settings.SIDE_MODULE and options.dylibs:
813+
# If there are any dynamic libraries on the command line then enable
814+
# `MAIN_MODULE` by default in order to produce JS code that can load them.
815+
if options.dylibs and not settings.MAIN_MODULE:
819816
default_setting('MAIN_MODULE', 2)
820817

821818
linker_args += calc_extra_ldflags(options)
@@ -917,8 +914,6 @@ def phase_linker_setup(options, linker_args): # noqa: C901, PLR0912, PLR0915
917914
if final_suffix in EXECUTABLE_EXTENSIONS:
918915
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)
919916
else:
920-
if options.shared and 'FAKE_DYLIBS' not in user_settings:
921-
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.')
922917
options.oformat = OFormat.OBJECT
923918

924919
if not options.oformat:
@@ -3016,8 +3011,7 @@ def phase_calculate_linker_inputs(options, linker_args):
30163011
else:
30173012
linker_args = filter_out_duplicate_fake_dynamic_libs(linker_args)
30183013

3019-
if settings.MAIN_MODULE:
3020-
process_dynamic_libs(options.dylibs, options.lib_dirs)
3014+
process_dynamic_libs(options.dylibs, options.lib_dirs)
30213015

30223016
return linker_args
30233017

0 commit comments

Comments
 (0)