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
4 changes: 4 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ See docs/process.md for more on how version tagging works.
notified of all IDBFS sync start and end events. (#26895)
- google-closure-compiler was updated to 20260429.0.0. (#26869)
Closure compiler now provides a native macOS arm64 binary for Apple Silicon.
- The FAKE_DYLIBS setting is now disabled by default. This means that `-shared`
will now produce real dynamic libraries by default. It also means that
`-sMAIN_MODULE=2` is not implicit if any real dynamic libraries are included
in the link. (#25930)

5.0.7 - 04/30/26
----------------
Expand Down
1 change: 0 additions & 1 deletion cmake/Modules/Platform/Emscripten.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ set(CMAKE_SYSTEM_NAME Emscripten)
set(CMAKE_SYSTEM_VERSION 1)

set(CMAKE_CROSSCOMPILING TRUE)
set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS FALSE)
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bradking, sorry to bother you again, but I'm having trouble landing this change because it seems like it interacts badly with the Modules/Platform/Emscripten-Clang.cmake that now exists upstream.

Even if I set set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-shared") I cannot seems to override the setting here:

https://github.com/Kitware/CMake/blob/61510710b12ddff97c2c22e146cdfb399daa2dad/Modules/Platform/Emscripten-Clang.cmake#L14

Basically it seems like the builtin support is somehow kicking in even though I don't want it. Is there some way I can disable the builtin stuff in using the this file?

It looks like there is some attempt to prevent the bad interaction here: https://github.com/Kitware/CMake/blob/61510710b12ddff97c2c22e146cdfb399daa2dad/Modules/Platform/Emscripten.cmake#L8-L12.

But that doesn't seem to stop the Emscripten-Clang.cmake from having its effects.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS isn't meant to be set by toolchain files. CMake needs to be taught that the Emscripten compiler now supports the -shared flag. What will its CMAKE_C_COMPILER_VERSION be?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see you started discussion in the CMake issue at https://gitlab.kitware.com/cmake/cmake/-/work_items/27240#note_1813569. Let's take the conversation there.


# Advertise Emscripten as a 32-bit platform (as opposed to
# CMAKE_SYSTEM_PROCESSOR=x86_64 for 64-bit platform), since some projects (e.g.
Expand Down
11 changes: 5 additions & 6 deletions site/source/docs/tools_reference/settings_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3354,13 +3354,12 @@ Default value: false
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 a dynamically linked
library.
This setting changes the behaviour of the ``-shared`` flag. When set to true
you get the old emscripten behaviour where the ``-shared`` flag actually
produces a normal object file (i.e. ``ld -r``). Then this is disabled, which
is the default behaviour, the `-shared` flag ask the same as :ref:`SIDE_MODULE`.

Default value: true
Default value: false

.. _executable:

Expand Down
11 changes: 5 additions & 6 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -2199,12 +2199,11 @@ var GROWABLE_ARRAYBUFFERS = false;
// 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 a dynamically linked
// library.
var FAKE_DYLIBS = true;
// This setting changes the behaviour of the ``-shared`` flag. When set to true
// you get the old emscripten behaviour where the ``-shared`` flag actually
// produces a normal object file (i.e. ``ld -r``). Then this is disabled, which
// is the default behaviour, the `-shared` flag ask the same as :ref:`SIDE_MODULE`.
var FAKE_DYLIBS = false;

// Add a #! line to generated JS file and make it executable. This is useful
// for building command line tools that run under node.
Expand Down
4 changes: 2 additions & 2 deletions test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -4059,8 +4059,8 @@ def dylink_testf(self, main, side=None, expected=None, force_c=False, main_cflag

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

# Verify that building with -sSIDE_MODULE is essentially the same as building with `-shared -fPIC -sFAKE_DYLIBS=0`.
flags = ['-shared', '-fPIC', '-sFAKE_DYLIBS=0']
# Verify that building with -sSIDE_MODULE is essentially the same as building with `-shared -fPIC`
flags = ['-shared', '-fPIC']
if isinstance(side, list):
# side is just a library
self.run_process([EMCC] + side + self.get_cflags() + flags + ['-o', so_file])
Expand Down
77 changes: 27 additions & 50 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -930,7 +930,8 @@ def test_cmake_explicit_generator(self):
# use -Wno-dev to suppress an irrelevant warning about the test files only.
cmd = [EMCMAKE, 'cmake', '-GNinja', '-Wno-dev', test_file('cmake/cpp_lib')]
self.run_process(cmd)
self.assertExists(self.get_dir() + '/build.ninja')
self.assertExists('build.ninja')
self.run_process(['ninja', '-v'])

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

Expand Down Expand Up @@ -1354,7 +1355,7 @@ def test_multiply_defined_libsymbols(self):
''')

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

self.emcc('a2.c', ['-r', '-L.', '-lA', '-o', 'a2.o'])
self.emcc('b2.c', ['-r', '-L.', '-lA', '-o', 'b2.o'])
Expand Down Expand Up @@ -1487,7 +1488,12 @@ def test_link_group_bitcode(self):
# We deliberately ignore duplicate input files in order to allow
# "libA.so" on the command line twice. This is not really .so support
# and the .so files are really object files.
def test_redundant_link(self):
@parameterized({
'': ([],),
'fake_dylibs': (['-sFAKE_DYLIBS'],),
})
def test_redundant_link(self, args):
self.cflags += args
create_file('libA.c', 'int mult() { return 1; }')
create_file('main.c', r'''
#include <stdio.h>
Expand All @@ -1499,7 +1505,7 @@ def test_redundant_link(self):
''')

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

Expand Down Expand Up @@ -2147,9 +2153,9 @@ def test_multidynamic_link(self, link_flags, lib_suffix):
''')

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

def test_warn_dylibs(self):
shared_suffixes = ['.so', '.dylib', '.dll']

for suffix in ('.o', '.bc', '.so', '.dylib', '.js', '.html'):
print(suffix)
cmd = [EMCC, test_file('hello_world.c'), '-o', 'out' + suffix]
if suffix in {'.o', '.bc'}:
cmd.append('-c')
if suffix in {'.dylib', '.so'}:
cmd.append('-shared')
err = self.run_process(cmd, stderr=PIPE).stderr
warning = 'linking a library with `-shared` will emit a static object file'
self.assertContainedIf(warning, err, suffix in shared_suffixes)

@crossplatform
@parameterized({
'O2': [['-O2']],
Expand Down Expand Up @@ -8446,19 +8438,6 @@ def test_side_module_folder_deps(self):
self.run_process([EMCC, test_file('hello_world.c'), '-sSIDE_MODULE', '-o', 'subdir/libside2.so', '-L', 'subdir', '-lside1'])
self.run_process([EMCC, test_file('hello_world.c'), '-sMAIN_MODULE', '-o', 'main.js', '-L', 'subdir', '-lside2'])

@crossplatform
def test_side_module_ignore(self):
self.run_process([EMCC, test_file('hello_world.c'), '-sSIDE_MODULE', '-o', 'libside.so'])

# Attempting to link statically against a side module (libside.so) should fail.
self.assert_fail([EMCC, '-L.', '-lside'], 'wasm-ld: error: unable to find library -lside')

# But a static library in the same location (libside.a) should take precedence.
self.run_process([EMCC, test_file('hello_world.c'), '-c'])
self.run_process([EMAR, 'cr', 'libside.a', 'hello_world.o'])
self.run_process([EMCC, '-L.', '-lside'])
self.assertContained('Hello, world!', self.run_js('a.out.js'))

@is_slow_test
@parameterized({
'': ([],),
Expand Down Expand Up @@ -12114,42 +12093,40 @@ def test_err(self):
def test_euidaccess(self):
self.do_other_test('test_euidaccess.c')

def test_shared_flag(self):
create_file('side.c', 'int foo;')
self.run_process([EMCC, '-shared', 'side.c', '-o', 'libother.so'])
def test_fake_dylibs(self):
create_file('other.c', 'int foo = 10;')
self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-fPIC', 'other.c', '-o', 'libother.so'])
self.assertIsObjectFile('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)
# Test that `-sFAKE_DYLIBS` flag causes object file generation and will generate a warning about
# dylink dependencies being ignored.
err = self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-fPIC', test_file('hello_world.c'), '-o', 'out.foo', 'libother.so'], stderr=PIPE).stderr
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_DYLIBS=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'],
err = self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-fPIC', test_file('hello_world.c'), '-o', 'out.js'],
stderr=PIPE).stderr
self.assertContained('warning: -shared/-r used with executable output suffix', err)
self.run_js('out.js')

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

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

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

Expand Down
9 changes: 7 additions & 2 deletions tools/building.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def get_building_env():
env['AR'] = EMAR
env['LD'] = EMCC
env['NM'] = LLVM_NM
env['LDSHARED'] = EMCC
env['LDSHARED'] = f'${EMCC} -shared'
env['RANLIB'] = EMRANLIB
env['EMSCRIPTEN_TOOLS'] = path_from_root('tools')
env['HOST_CC'] = CLANG_CC
Expand Down Expand Up @@ -165,6 +165,12 @@ def lld_flags_for_executable(external_symbols):
stub = create_stub_object(external_symbols)
cmd.append(stub)

if settings.FAKE_DYLIBS:
cmd.append('-Bstatic')
else:
# wasm-ld still default to static linking by default. If that ever changes we can remove this line.
cmd.append('-Bdynamic')
Comment thread
sbc100 marked this conversation as resolved.

if not settings.ERROR_ON_UNDEFINED_SYMBOLS:
cmd.append('--import-undefined')

Expand Down Expand Up @@ -203,7 +209,6 @@ def lld_flags_for_executable(external_symbols):
c_exports = [e for e in c_exports if e not in external_symbols]
c_exports += settings.REQUIRED_EXPORTS
if settings.MAIN_MODULE:
cmd.append('-Bdynamic')
c_exports += side_module_external_deps(external_symbols)
for export in c_exports:
if settings.ERROR_ON_UNDEFINED_SYMBOLS:
Expand Down
3 changes: 1 addition & 2 deletions tools/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@

@unique
class OFormat(Enum):
# Output a relocatable object file. We use this
# today for `-r` and `-shared`.
# Output a relocatable object file. i.e. `-r` linker flag
OBJECT = auto()
WASM = auto()
JS = auto()
Expand Down
16 changes: 5 additions & 11 deletions tools/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,17 +805,14 @@ def phase_linker_setup(options, linker_args): # noqa: C901, PLR0912, PLR0915

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)

if not settings.FAKE_DYLIBS:
if not settings.SIDE_MODULE and 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:
# If there are any dynamic libraries on the command line then enable
# `MAIN_MODULE` by default in order to produce JS code that can load them.
if options.dylibs and not settings.MAIN_MODULE:
default_setting('MAIN_MODULE', 2)

linker_args += calc_extra_ldflags(options)
Expand Down Expand Up @@ -917,8 +914,6 @@ def phase_linker_setup(options, linker_args): # noqa: C901, PLR0912, PLR0915
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 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 @@ -3016,8 +3011,7 @@ def phase_calculate_linker_inputs(options, linker_args):
else:
linker_args = filter_out_duplicate_fake_dynamic_libs(linker_args)

if settings.MAIN_MODULE:
process_dynamic_libs(options.dylibs, options.lib_dirs)
process_dynamic_libs(options.dylibs, options.lib_dirs)

return linker_args

Expand Down
Loading