Skip to content

Commit d7505a5

Browse files
committed
Build shared libraries by default with -shared
The old behavior was to build fake/dummy shared libraries which were really just normal object files. This old behavior can still achieved by settings the new `FAKE_DYLIBS` settings.
1 parent c6833e4 commit d7505a5

File tree

6 files changed

+74
-64
lines changed

6 files changed

+74
-64
lines changed

cmake/Modules/Platform/Emscripten.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ 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)
21+
set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE)
2222

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

src/settings.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2230,3 +2230,9 @@ 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. When set to true
2235+
// you get the old emscripten behaivour where the ``-shared`` flag actually
2236+
// produces a normal object file (i.e. ``ld -r``). The default behaviour is that
2237+
// `-shared` is the as :ref:`SIDE_MODULE`.
2238+
var FAKE_DYLIBS = false;

test/test_other.py

Lines changed: 20 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,10 +1235,10 @@ def test_odd_suffixes(self):
12351235
shutil.copy(test_file('hello_world.c'), 'test.' + suffix)
12361236
self.do_runf('test.' + suffix, 'hello, world!')
12371237

1238-
for suffix in ('lo'):
1238+
for suffix in ('lo',):
12391239
self.clear()
12401240
print(suffix)
1241-
self.run_process([EMCC, test_file('hello_world.c'), '-shared', '-o', 'binary.' + suffix])
1241+
self.run_process([EMCC, test_file('hello_world.c'), '-sFAKE_DYLIBS', '-shared', '-o', 'binary.' + suffix])
12421242
self.run_process([EMCC, 'binary.' + suffix])
12431243
self.assertContained('hello, world!', self.run_js('a.out.js'))
12441244

@@ -1419,7 +1419,7 @@ def test_multiply_defined_libsymbols(self):
14191419
''')
14201420

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

14241424
self.emcc('a2.c', ['-r', '-L.', '-lA', '-o', 'a2.o'])
14251425
self.emcc('b2.c', ['-r', '-L.', '-lA', '-o', 'b2.o'])
@@ -1552,7 +1552,12 @@ def test_link_group_bitcode(self):
15521552
# We deliberately ignore duplicate input files in order to allow
15531553
# "libA.so" on the command line twice. This is not really .so support
15541554
# and the .so files are really object files.
1555-
def test_redundant_link(self):
1555+
@parameterized({
1556+
'': ([],),
1557+
'fake_dylibs': (['-sFAKE_DYLIBS'],),
1558+
})
1559+
def test_redundant_link(self, args):
1560+
self.cflags += args
15561561
create_file('libA.c', 'int mult() { return 1; }')
15571562
create_file('main.c', r'''
15581563
#include <stdio.h>
@@ -1564,7 +1569,7 @@ def test_redundant_link(self):
15641569
''')
15651570

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

@@ -2159,9 +2164,9 @@ def test_multidynamic_link(self, link_flags, lib_suffix):
21592164
''')
21602165

21612166
# Build libfile normally into an .so
2162-
self.run_process([EMCC, 'libdir/libfile.c', '-shared', '-o', 'libdir/libfile.so' + lib_suffix])
2167+
self.run_process([EMCC, 'libdir/libfile.c', '-sFAKE_DYLIBS', '-shared', '-fPIC', '-o', 'libdir/libfile.so' + lib_suffix])
21632168
# Build libother and dynamically link it to libfile
2164-
self.run_process([EMCC, '-Llibdir', 'libdir/libother.c'] + link_flags + ['-shared', '-o', 'libdir/libother.so'])
2169+
self.run_process([EMCC, '-Llibdir', 'libdir/libother.c'] + link_flags + ['-sFAKE_DYLIBS', '-shared', '-fPIC', '-o', 'libdir/libother.so'])
21652170
# Build the main file, linking in both the libs
21662171
self.run_process([EMCC, '-Llibdir', os.path.join('main.c')] + link_flags + ['-lother', '-c'])
21672172
print('...')
@@ -4906,20 +4911,6 @@ def test_valid_abspath_2(self):
49064911
self.run_process(cmd)
49074912
self.assertContained('hello, world!', self.run_js('a.out.js'))
49084913

4909-
def test_warn_dylibs(self):
4910-
shared_suffixes = ['.so', '.dylib', '.dll']
4911-
4912-
for suffix in ('.o', '.bc', '.so', '.dylib', '.js', '.html'):
4913-
print(suffix)
4914-
cmd = [EMCC, test_file('hello_world.c'), '-o', 'out' + suffix]
4915-
if suffix in ['.o', '.bc']:
4916-
cmd.append('-c')
4917-
if suffix in ['.dylib', '.so']:
4918-
cmd.append('-shared')
4919-
err = self.run_process(cmd, stderr=PIPE).stderr
4920-
warning = 'linking a library with `-shared` will emit a static object file'
4921-
self.assertContainedIf(warning, err, suffix in shared_suffixes)
4922-
49234914
@crossplatform
49244915
@parameterized({
49254916
'O2': [['-O2']],
@@ -8369,20 +8360,6 @@ def test_side_module_folder_deps(self):
83698360
self.run_process([EMCC, test_file('hello_world.c'), '-sSIDE_MODULE', '-o', 'subdir/libside2.so', '-L', 'subdir', '-lside1'])
83708361
self.run_process([EMCC, test_file('hello_world.c'), '-sMAIN_MODULE', '-o', 'main.js', '-L', 'subdir', '-lside2'])
83718362

8372-
@crossplatform
8373-
def test_side_module_ignore(self):
8374-
self.run_process([EMCC, test_file('hello_world.c'), '-sSIDE_MODULE', '-o', 'libside.so'])
8375-
8376-
# Attempting to link statically against a side module (libside.so) should fail.
8377-
err = self.expect_fail([EMCC, '-L.', '-lside'])
8378-
self.assertContained(r'error: attempted static link of dynamic object \.[/\\]libside.so', err, regex=True)
8379-
8380-
# But a static library in the same location (libside.a) should take precedence.
8381-
self.run_process([EMCC, test_file('hello_world.c'), '-c'])
8382-
self.run_process([EMAR, 'cr', 'libside.a', 'hello_world.o'])
8383-
self.run_process([EMCC, '-L.', '-lside'])
8384-
self.assertContained('hello, world!', self.run_js('a.out.js'))
8385-
83868363
@is_slow_test
83878364
@parameterized({
83888365
'': ([],),
@@ -11903,23 +11880,24 @@ def test_err(self):
1190311880
def test_euidaccess(self):
1190411881
self.do_other_test('test_euidaccess.c')
1190511882

11906-
def test_shared_flag(self):
11907-
self.run_process([EMCC, '-shared', test_file('hello_world.c'), '-o', 'libother.so'])
11883+
def test_fake_dylibs(self):
11884+
self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-fPIC', test_file('hello_world.c'), '-o', 'libother.so'])
11885+
self.assertIsObjectFile('libother.so')
1190811886

11909-
# Test that `-shared` flag causes object file generation but gives a warning
11910-
err = self.run_process([EMCC, '-shared', test_file('hello_world.c'), '-o', 'out.foo', 'libother.so'], stderr=PIPE).stderr
11911-
self.assertContained('linking a library with `-shared` will emit a static object', err)
11887+
# Test that `-sFAKE_DYLIBS` flag causes object file generation and will generate a warning about
11888+
# dylink dependencies being ignored.
11889+
err = self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-fPIC', test_file('hello_world.c'), '-o', 'out.foo', 'libother.so'], stderr=PIPE).stderr
1191211890
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)
1191311891
self.assertIsObjectFile('out.foo')
1191411892

1191511893
# Test that using an executable output name overrides the `-shared` flag, but produces a warning.
11916-
err = self.run_process([EMCC, '-shared', test_file('hello_world.c'), '-o', 'out.js'],
11894+
err = self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-fPIC', test_file('hello_world.c'), '-o', 'out.js'],
1191711895
stderr=PIPE).stderr
1191811896
self.assertContained('warning: -shared/-r used with executable output suffix', err)
1191911897
self.run_js('out.js')
1192011898

1192111899
def test_shared_soname(self):
11922-
self.run_process([EMCC, '-shared', '-Wl,-soname', '-Wl,libfoo.so.13', test_file('hello_world.c'), '-lc', '-o', 'libfoo.so'])
11900+
self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-Wl,-soname', '-Wl,libfoo.so.13', test_file('hello_world.c'), '-lc', '-o', 'libfoo.so'])
1192311901
self.run_process([EMCC, '-sSTRICT', 'libfoo.so'])
1192411902
self.assertContained('hello, world!', self.run_js('a.out.js'))
1192511903

tools/building.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ def lld_flags_for_executable(external_symbols):
170170
stub = create_stub_object(external_symbols)
171171
cmd.append(stub)
172172

173+
if not settings.FAKE_DYLIBS:
174+
cmd.append('-Bdynamic')
175+
173176
if not settings.ERROR_ON_UNDEFINED_SYMBOLS:
174177
cmd.append('--import-undefined')
175178

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: 43 additions & 21 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,15 @@ 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 options.shared and not settings.FAKE_DYLIBS:
864+
default_setting('SIDE_MODULE', 1)
865+
default_setting('RELOCATABLE', 1)
866+
867+
options.dylibs = get_dylibs(options, linker_args)
868+
if not settings.MAIN_MODULE and not settings.SIDE_MODULE and options.dylibs:
869+
default_setting('MAIN_MODULE', 2)
870+
846871
linker_args += calc_extra_ldflags(options)
847872

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

937962
# If no output format was specified we try to deduce the format based on
938963
# 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.
964+
if not options.oformat and (options.relocatable or (options.shared and settings.FAKE_DYLIBS)):
965+
# With FAKE_DYLIBS we generate an normal object file rather than an shared object.
966+
# This is linked with `wasm-ld --relocatable` or (`llvm-link` in the case of LTO).
944967
if final_suffix in EXECUTABLE_EXTENSIONS:
945968
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)
946969
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.')
949970
options.oformat = OFormat.OBJECT
950971

951972
if not options.oformat:
@@ -2752,10 +2773,10 @@ def map_to_js_libs(library_name):
27522773

27532774

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

2758-
# Process `-l` and `--js-library` flags
27592780
for flag in flags:
27602781
if flag.startswith('--js-library='):
27612782
js_lib = flag.split('=', 1)[1]
@@ -2793,22 +2814,22 @@ def process_libraries(options, flags):
27932814
continue
27942815

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

2811-
if found_dylib:
2832+
if found_fake_dylib:
28122833
continue
28132834

28142835
new_flags.append(flag)
@@ -2876,7 +2897,7 @@ def replacement(self):
28762897

28772898

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

28902911

2891-
def filter_out_duplicate_dynamic_libs(inputs):
2912+
def filter_out_duplicate_fake_dynamic_libs(inputs):
2913+
"""Filter out duplicate "fake" shared libraries (intermediate object files).
2914+
2915+
See test_core.py:test_redundant_link
2916+
"""
28922917
seen = set()
28932918

2894-
# Filter out duplicate "fake" shared libraries (intermediate object files).
2895-
# See test_core.py:test_redundant_link
28962919
def check(input_file):
28972920
if get_file_suffix(input_file) in DYLIB_EXTENSIONS and not building.is_wasm_dylib(input_file):
28982921
abspath = os.path.abspath(input_file)
@@ -3108,11 +3131,10 @@ def phase_calculate_linker_inputs(options, linker_args):
31083131
if options.oformat == OFormat.OBJECT or options.ignore_dynamic_linking:
31093132
linker_args = filter_out_fake_dynamic_libs(options, linker_args)
31103133
else:
3111-
linker_args = filter_out_duplicate_dynamic_libs(linker_args)
3134+
linker_args = filter_out_duplicate_fake_dynamic_libs(linker_args)
31123135

3113-
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)
3136+
if not settings.SIDE_MODULE and not settings.FAKE_DYLIBS:
3137+
process_dynamic_libs(options.dylibs, options.lib_dirs)
31163138

31173139
return linker_args
31183140

0 commit comments

Comments
 (0)