Skip to content
Draft
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
2 changes: 1 addition & 1 deletion cmake/Modules/Platform/Emscripten.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ set(CMAKE_SYSTEM_NAME Emscripten)
set(CMAKE_SYSTEM_VERSION 1)

set(CMAKE_CROSSCOMPILING TRUE)
set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS FALSE)
set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE)

# 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
6 changes: 6 additions & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -2230,3 +2230,9 @@ var WASM_JS_TYPES = false;
// CROSS_ORIGIN uses an inline worker to instead load the worker script
// indirectly using `importScripts`
var CROSS_ORIGIN = false;

// This settings changes the behaviour of the ``-shared`` flag. When set to true
// you get the old emscripten behaivour where the ``-shared`` flag actually
// produces a normal object file (i.e. ``ld -r``). The default behaviour is that
// `-shared` is the as :ref:`SIDE_MODULE`.
var FAKE_DYLIBS = false;
62 changes: 20 additions & 42 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -1235,10 +1235,10 @@ def test_odd_suffixes(self):
shutil.copy(test_file('hello_world.c'), 'test.' + suffix)
self.do_runf('test.' + suffix, 'hello, world!')

for suffix in ('lo'):
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 @@ -1419,7 +1419,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 @@ -1552,7 +1552,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 @@ -1564,7 +1569,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 @@ -2159,9 +2164,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 @@ -4906,20 +4911,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 @@ -8369,20 +8360,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.
err = self.expect_fail([EMCC, '-L.', '-lside'])
self.assertContained(r'error: attempted static link of dynamic object \.[/\\]libside.so', err, regex=True)

# 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 @@ -11903,23 +11880,24 @@ def test_err(self):
def test_euidaccess(self):
self.do_other_test('test_euidaccess.c')

def test_shared_flag(self):
self.run_process([EMCC, '-shared', test_file('hello_world.c'), '-o', 'libother.so'])
def test_fake_dylibs(self):
self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-fPIC', test_file('hello_world.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 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'))

Expand Down
4 changes: 3 additions & 1 deletion tools/building.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ def lld_flags_for_executable(external_symbols):
stub = create_stub_object(external_symbols)
cmd.append(stub)

if not settings.FAKE_DYLIBS:
cmd.append('-Bdynamic')

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

Expand Down Expand Up @@ -208,7 +211,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
1 change: 1 addition & 0 deletions tools/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class EmccOptions:
dash_M = False
dash_S = False
dash_c = False
dylibs: List[str] = []
embed_files: List[str] = []
emit_symbol_map = False
emit_tsd = ''
Expand Down
64 changes: 43 additions & 21 deletions tools/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,22 @@ def setup_sanitizers(options):
settings.LOAD_SOURCE_MAP = 1


def get_dylibs(options, linker_args):
"""Find all the Wasm dynanamic libraries specified on the command line,
either via `-lfoo` or vis `libfoo.so` directly."""

dylibs = []
for arg in linker_args:
if arg.startswith('-l'):
for ext in DYLIB_EXTENSIONS:
path = find_library('lib' + arg[2:] + ext, options.lib_dirs)
if path and building.is_wasm_dylib(path):
dylibs.append(path)
elif building.is_wasm_dylib(arg):
dylibs.append(arg)
return dylibs


@ToolchainProfiler.profile_block('linker_setup')
def phase_linker_setup(options, linker_args): # noqa: C901, PLR0912, PLR0915
"""Future modifications should consider refactoring to reduce complexity.
Expand All @@ -843,6 +859,15 @@ def phase_linker_setup(options, linker_args): # noqa: C901, PLR0912, PLR0915
setup_environment_settings()

apply_library_settings(linker_args)

if options.shared and not settings.FAKE_DYLIBS:
default_setting('SIDE_MODULE', 1)
default_setting('RELOCATABLE', 1)

options.dylibs = get_dylibs(options, linker_args)
if not settings.MAIN_MODULE and not settings.SIDE_MODULE and options.dylibs:
default_setting('MAIN_MODULE', 2)

linker_args += calc_extra_ldflags(options)

# We used to do this check during on startup during `check_sanity`, but
Expand Down Expand Up @@ -936,16 +961,12 @@ def phase_linker_setup(options, linker_args): # noqa: C901, PLR0912, PLR0915

# If no output format was specified we try to deduce the format based on
# the output filename extension
if not options.oformat and (options.relocatable or (options.shared and not settings.SIDE_MODULE)):
# Until we have a better story for actually producing runtime shared libraries
# we support a compatibility mode where shared libraries are actually just
# object files linked with `wasm-ld --relocatable` or `llvm-link` in the case
# of LTO.
if not options.oformat and (options.relocatable or (options.shared and settings.FAKE_DYLIBS)):
# With FAKE_DYLIBS we generate an normal object file rather than an shared object.
# This is linked with `wasm-ld --relocatable` or (`llvm-link` in the case of LTO).
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:
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.')
options.oformat = OFormat.OBJECT

if not options.oformat:
Expand Down Expand Up @@ -2752,10 +2773,10 @@ def map_to_js_libs(library_name):


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

# Process `-l` and `--js-library` flags
for flag in flags:
if flag.startswith('--js-library='):
js_lib = flag.split('=', 1)[1]
Expand Down Expand Up @@ -2793,22 +2814,22 @@ def process_libraries(options, flags):
continue

static_lib = f'lib{lib}.a'
if not settings.RELOCATABLE and not find_library(static_lib, options.lib_dirs):
if not find_library(static_lib, options.lib_dirs):
# Normally we can rely on the native linker to expand `-l` args.
# However, emscripten also supports `.so` files that are actually just
# regular object file. This means we need to support `.so` files even
# when statically linking. The native linker (wasm-ld) will otherwise
# ignore .so files in this mode.
found_dylib = False
found_fake_dylib = False
for ext in DYLIB_EXTENSIONS:
name = 'lib' + lib + ext
path = find_library(name, options.lib_dirs)
if path:
found_dylib = True
if path and not building.is_wasm_dylib(path):
found_fake_dylib = True
new_flags.append(path)
break

if found_dylib:
if found_fake_dylib:
continue

new_flags.append(flag)
Expand Down Expand Up @@ -2876,7 +2897,7 @@ def replacement(self):


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


def filter_out_duplicate_dynamic_libs(inputs):
def filter_out_duplicate_fake_dynamic_libs(inputs):
"""Filter out duplicate "fake" shared libraries (intermediate object files).

See test_core.py:test_redundant_link
"""
seen = set()

# Filter out duplicate "fake" shared libraries (intermediate object files).
# See test_core.py:test_redundant_link
def check(input_file):
if get_file_suffix(input_file) in DYLIB_EXTENSIONS and not building.is_wasm_dylib(input_file):
abspath = os.path.abspath(input_file)
Expand Down Expand Up @@ -3108,11 +3131,10 @@ def phase_calculate_linker_inputs(options, linker_args):
if options.oformat == OFormat.OBJECT or options.ignore_dynamic_linking:
linker_args = filter_out_fake_dynamic_libs(options, linker_args)
else:
linker_args = filter_out_duplicate_dynamic_libs(linker_args)
linker_args = filter_out_duplicate_fake_dynamic_libs(linker_args)

if settings.MAIN_MODULE:
dylibs = [a for a in linker_args if building.is_wasm_dylib(a)]
process_dynamic_libs(dylibs, options.lib_dirs)
if not settings.SIDE_MODULE and not settings.FAKE_DYLIBS:
process_dynamic_libs(options.dylibs, options.lib_dirs)

return linker_args

Expand Down