Skip to content

Commit d2bc4e4

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 b406fc1 commit d2bc4e4

9 files changed

Lines changed: 56 additions & 80 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 behaviour where the ``-shared`` flag actually
3359+
produces a normal object file (i.e. ``ld -r``). Then this is disabled, which
3360+
is the default behaviour, the `-shared` flag ask the same 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 behaviour where the ``-shared`` flag actually
2204+
// produces a normal object file (i.e. ``ld -r``). Then this is disabled, which
2205+
// is the default behaviour, the `-shared` flag ask the same 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: 27 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -930,7 +930,8 @@ def test_cmake_explicit_generator(self):
930930
# use -Wno-dev to suppress an irrelevant warning about the test files only.
931931
cmd = [EMCMAKE, 'cmake', '-GNinja', '-Wno-dev', test_file('cmake/cpp_lib')]
932932
self.run_process(cmd)
933-
self.assertExists(self.get_dir() + '/build.ninja')
933+
self.assertExists('build.ninja')
934+
self.run_process(['ninja', '-v'])
934935

935936
# Tests that it's possible to pass C++17 or GNU++17 build modes to CMake by building code that
936937
# needs C++17 (embind)
@@ -1173,7 +1174,7 @@ def test_odd_suffixes(self):
11731174
for suffix in ('lo',):
11741175
self.clear()
11751176
print(suffix)
1176-
self.run_process([EMCC, test_file('hello_world.c'), '-shared', '-o', 'binary.' + suffix])
1177+
self.run_process([EMCC, test_file('hello_world.c'), '-sFAKE_DYLIBS', '-shared', '-o', 'binary.' + suffix])
11771178
self.run_process([EMCC, 'binary.' + suffix])
11781179
self.assertContained('Hello, world!', self.run_js('a.out.js'))
11791180

@@ -1354,7 +1355,7 @@ def test_multiply_defined_libsymbols(self):
13541355
''')
13551356

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

13591360
self.emcc('a2.c', ['-r', '-L.', '-lA', '-o', 'a2.o'])
13601361
self.emcc('b2.c', ['-r', '-L.', '-lA', '-o', 'b2.o'])
@@ -1487,7 +1488,12 @@ def test_link_group_bitcode(self):
14871488
# We deliberately ignore duplicate input files in order to allow
14881489
# "libA.so" on the command line twice. This is not really .so support
14891490
# and the .so files are really object files.
1490-
def test_redundant_link(self):
1491+
@parameterized({
1492+
'': ([],),
1493+
'fake_dylibs': (['-sFAKE_DYLIBS'],),
1494+
})
1495+
def test_redundant_link(self, args):
1496+
self.cflags += args
14911497
create_file('libA.c', 'int mult() { return 1; }')
14921498
create_file('main.c', r'''
14931499
#include <stdio.h>
@@ -1499,7 +1505,7 @@ def test_redundant_link(self):
14991505
''')
15001506

15011507
self.cflags.remove('-Werror')
1502-
self.emcc('libA.c', ['-shared', '-o', 'libA.so'])
1508+
self.emcc('libA.c', ['-fPIC', '-shared', '-o', 'libA.so'])
15031509
self.emcc('main.c', ['libA.so', 'libA.so', '-o', 'a.out.js'])
15041510
self.assertContained('result: 1', self.run_js('a.out.js'))
15051511

@@ -2147,9 +2153,9 @@ def test_multidynamic_link(self, link_flags, lib_suffix):
21472153
''')
21482154

21492155
# Build libfile normally into an .so
2150-
self.run_process([EMCC, 'libdir/libfile.c', '-shared', '-o', 'libdir/libfile.so' + lib_suffix])
2156+
self.run_process([EMCC, 'libdir/libfile.c', '-sFAKE_DYLIBS', '-shared', '-fPIC', '-o', 'libdir/libfile.so' + lib_suffix])
21512157
# Build libother and dynamically link it to libfile
2152-
self.run_process([EMCC, '-Llibdir', 'libdir/libother.c'] + link_flags + ['-shared', '-o', 'libdir/libother.so'])
2158+
self.run_process([EMCC, '-Llibdir', 'libdir/libother.c'] + link_flags + ['-sFAKE_DYLIBS', '-shared', '-fPIC', '-o', 'libdir/libother.so'])
21532159
# Build the main file, linking in both the libs
21542160
self.run_process([EMCC, '-Llibdir', os.path.join('main.c')] + link_flags + ['-lother', '-c'])
21552161
print('...')
@@ -4940,20 +4946,6 @@ def test_valid_abspath_2(self):
49404946
self.run_process(cmd)
49414947
self.assertContained('Hello, world!', self.run_js('a.out.js'))
49424948

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-
49574949
@crossplatform
49584950
@parameterized({
49594951
'O2': [['-O2']],
@@ -8446,19 +8438,6 @@ def test_side_module_folder_deps(self):
84468438
self.run_process([EMCC, test_file('hello_world.c'), '-sSIDE_MODULE', '-o', 'subdir/libside2.so', '-L', 'subdir', '-lside1'])
84478439
self.run_process([EMCC, test_file('hello_world.c'), '-sMAIN_MODULE', '-o', 'main.js', '-L', 'subdir', '-lside2'])
84488440

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-
84628441
@is_slow_test
84638442
@parameterized({
84648443
'': ([],),
@@ -12114,42 +12093,40 @@ def test_err(self):
1211412093
def test_euidaccess(self):
1211512094
self.do_other_test('test_euidaccess.c')
1211612095

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

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)
12101+
# Test that `-sFAKE_DYLIBS` flag causes object file generation and will generate a warning about
12102+
# dylink dependencies being ignored.
12103+
err = self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-fPIC', test_file('hello_world.c'), '-o', 'out.foo', 'libother.so'], stderr=PIPE).stderr
1212412104
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)
1212512105
self.assertIsObjectFile('out.foo')
1212612106

1212712107
# Test that adding `-sFAKE_DYLIBS=0` build a real side module
1212812108
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)
1213012109
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)
1213112110
self.assertIsWasmDylib('out.foo')
1213212111

1213312112
# 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'],
12113+
err = self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-fPIC', test_file('hello_world.c'), '-o', 'out.js'],
1213512114
stderr=PIPE).stderr
1213612115
self.assertContained('warning: -shared/-r used with executable output suffix', err)
1213712116
self.run_js('out.js')
1213812117

1213912118
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'])
12119+
self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-Wl,-soname', '-Wl,libfoo.so.13', test_file('hello_world.c'), '-lc', '-o', 'libfoo.so'])
1214112120
self.run_process([EMCC, '-sSTRICT', 'libfoo.so'])
1214212121
self.assertContained('Hello, world!', self.run_js('a.out.js'))
1214312122

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)
12123+
def test_shared_flag(self):
12124+
# Test that `-shared` flag causes wasm dylib generation
12125+
self.run_process([EMCC, '-shared', '-fPIC', test_file('hello_world.c'), '-o', 'out.foo'])
1214812126
self.assertIsWasmDylib('out.foo')
1214912127

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
12128+
# Test that `-shared` causes wasm dylib generation warning even if given executable output name.
12129+
err = self.run_process([EMCC, '-shared', '-fPIC', test_file('hello_world.c'), '-o', 'out.wasm'], stderr=PIPE).stderr
1215312130
self.assertNotContained('warning: -shared/-r used with executable output suffix', err)
1215412131
self.assertIsWasmDylib('out.wasm')
1215512132

tools/building.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def get_building_env():
7272
env['AR'] = EMAR
7373
env['LD'] = EMCC
7474
env['NM'] = LLVM_NM
75-
env['LDSHARED'] = EMCC
75+
env['LDSHARED'] = f'${EMCC} -shared'
7676
env['RANLIB'] = EMRANLIB
7777
env['EMSCRIPTEN_TOOLS'] = path_from_root('tools')
7878
env['HOST_CC'] = CLANG_CC
@@ -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/cmdline.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@
5454

5555
@unique
5656
class OFormat(Enum):
57-
# Output a relocatable object file. We use this
58-
# today for `-r` and `-shared`.
57+
# Output a relocatable object file. i.e. `-r` linker flag
5958
OBJECT = auto()
6059
WASM = auto()
6160
JS = auto()

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)