Skip to content

Commit 92fc305

Browse files
committed
Map user-specified system libraries to correct variants
Normally users don't specify system libraries such as libc or compiler-rt on the command line. However, when they do it makes sense map them to correct variant. For example, linking with `-pthread` + `-lc` will now end up including `libc-mt.a` rather than `libc.a`. Fixes: #14341
1 parent c024987 commit 92fc305

File tree

3 files changed

+56
-36
lines changed

3 files changed

+56
-36
lines changed

emcc.py

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -203,15 +203,12 @@ def __init__(self, args):
203203
self.has_dash_c = False
204204
self.has_dash_E = False
205205
self.has_dash_S = False
206-
self.libs = []
207206
self.link_flags = []
208207
self.lib_dirs = []
209208
self.forced_stdlibs = []
210209

211210

212211
def add_link_flag(state, i, f):
213-
if f.startswith('-l'):
214-
state.libs.append((i, f[2:]))
215212
if f.startswith('-L'):
216213
state.lib_dirs.append(f[2:])
217214

@@ -638,7 +635,7 @@ def check(input_file):
638635
else:
639636
return True
640637

641-
return [f for f in inputs if check(f[1])]
638+
return [f for f in inputs if check(f)]
642639

643640

644641
def filter_out_duplicate_dynamic_libs(inputs):
@@ -654,7 +651,7 @@ def check(input_file):
654651
seen.add(abspath)
655652
return True
656653

657-
return [f for f in inputs if check(f[1])]
654+
return [f for f in inputs if check(f)]
658655

659656

660657
def process_dynamic_libs(dylibs, lib_dirs):
@@ -1053,7 +1050,7 @@ def run(args):
10531050
if state.mode == Mode.POST_LINK_ONLY:
10541051
settings.limit_settings(None)
10551052
target, wasm_target = phase_linker_setup(options, state, newargs, settings_map)
1056-
process_libraries(state.libs, state.lib_dirs, [])
1053+
process_libraries(state.link_flags, state.lib_dirs, [])
10571054
if len(input_files) != 1:
10581055
exit_with_error('--post-link requires a single input file')
10591056
phase_post_link(options, state, input_files[0][1], wasm_target, target)
@@ -1116,24 +1113,23 @@ def phase_calculate_linker_inputs(options, state, linker_inputs):
11161113
state.link_flags = filter_link_flags(state.link_flags, using_lld)
11171114

11181115
# Decide what we will link
1119-
consumed = process_libraries(state.libs, state.lib_dirs, linker_inputs)
1120-
# Filter out libraries that are actually JS libs
1121-
state.link_flags = [l for l in state.link_flags if l[0] not in consumed]
1116+
state.link_flags = process_libraries(state.link_flags, state.lib_dirs, linker_inputs)
1117+
1118+
linker_args = [val for _, val in sorted(linker_inputs + state.link_flags)]
11221119

11231120
# If we are linking to an intermediate object then ignore other
11241121
# "fake" dynamic libraries, since otherwise we will end up with
11251122
# multiple copies in the final executable.
11261123
if options.oformat == OFormat.OBJECT or options.ignore_dynamic_linking:
1127-
linker_inputs = filter_out_dynamic_libs(options, linker_inputs)
1124+
linker_args = filter_out_dynamic_libs(options, linker_args)
11281125
else:
1129-
linker_inputs = filter_out_duplicate_dynamic_libs(linker_inputs)
1126+
linker_args = filter_out_duplicate_dynamic_libs(linker_args)
11301127

11311128
if settings.MAIN_MODULE:
1132-
dylibs = [i[1] for i in linker_inputs if building.is_wasm_dylib(i[1])]
1129+
dylibs = [a for a in linker_args if building.is_wasm_dylib(a)]
11331130
process_dynamic_libs(dylibs, state.lib_dirs)
11341131

1135-
linker_arguments = [val for _, val in sorted(linker_inputs + state.link_flags)]
1136-
return linker_arguments
1132+
return linker_args
11371133

11381134

11391135
@ToolchainProfiler.profile_block('parse arguments')
@@ -1244,7 +1240,7 @@ def phase_setup(options, state, newargs, settings_map):
12441240
# library and attempt to find a library by the same name in our own library path.
12451241
# TODO(sbc): Do we really need this feature? See test_other.py:test_local_link
12461242
libname = strip_prefix(unsuffixed_basename(arg), 'lib')
1247-
state.libs.append((i, libname))
1243+
add_link_flag(state, i, '-l' + libname)
12481244
else:
12491245
input_files.append((i, arg))
12501246
elif arg.startswith('-L'):
@@ -1986,7 +1982,7 @@ def check_memory_setting(setting):
19861982
# When not declaring wasm module exports in outer scope one by one, disable minifying
19871983
# wasm module export names so that the names can be passed directly to the outer scope.
19881984
# Also, if using library_exports.js API, disable minification so that the feature can work.
1989-
if not settings.DECLARE_ASM_MODULE_EXPORTS or 'exports.js' in [x for _, x in state.libs]:
1985+
if not settings.DECLARE_ASM_MODULE_EXPORTS or '-lexports.js' in [x for _, x in state.link_flags]:
19901986
settings.MINIFY_ASMJS_EXPORT_NAMES = 0
19911987

19921988
# Enable minification of wasm imports and exports when appropriate, if we
@@ -3506,13 +3502,26 @@ def find_library(lib, lib_dirs):
35063502
return None
35073503

35083504

3509-
def process_libraries(libs, lib_dirs, linker_inputs):
3505+
def process_libraries(link_flags, lib_dirs, linker_inputs):
3506+
new_flags = []
35103507
libraries = []
3511-
consumed = []
35123508
suffixes = STATICLIB_ENDINGS + DYNAMICLIB_ENDINGS
3509+
system_libs_map = system_libs.Library.get_usable_variations()
35133510

35143511
# Find library files
3515-
for i, lib in libs:
3512+
for i, flag in link_flags:
3513+
if not flag.startswith('-l'):
3514+
new_flags.append((i, flag))
3515+
continue
3516+
lib = strip_prefix(flag, '-l')
3517+
# We don't need to resolve system libraries to absolute paths here, we can just
3518+
# let wasm-ld handle that. However, we do want to map to the correct variant.
3519+
# For example we map `-lc` to `-lc-mt` if we are building with threading support.
3520+
if 'lib' + lib in system_libs_map:
3521+
lib = system_libs_map['lib' + lib]
3522+
new_flags.append((i, '-l' + strip_prefix(lib.get_base_name(), 'lib')))
3523+
continue
3524+
35163525
logger.debug('looking for library "%s"', lib)
35173526

35183527
path = None
@@ -3524,22 +3533,20 @@ def process_libraries(libs, lib_dirs, linker_inputs):
35243533

35253534
if path:
35263535
linker_inputs.append((i, path))
3527-
consumed.append(i)
3528-
else:
3529-
jslibs = building.map_to_js_libs(lib)
3530-
if jslibs is not None:
3531-
libraries += [(i, jslib) for jslib in jslibs]
3532-
consumed.append(i)
3533-
elif building.map_and_apply_to_settings(lib):
3534-
consumed.append(i)
3536+
continue
3537+
jslibs = building.map_to_js_libs(lib)
3538+
if jslibs is not None:
3539+
libraries += [(i, jslib) for jslib in jslibs]
3540+
elif not building.map_and_apply_to_settings(lib):
3541+
new_flags.append((i, flag))
35353542

35363543
settings.SYSTEM_JS_LIBRARIES += libraries
35373544

35383545
# At this point processing SYSTEM_JS_LIBRARIES is finished, no more items will be added to it.
35393546
# Sort the input list from (order, lib_name) pairs to a flat array in the right order.
35403547
settings.SYSTEM_JS_LIBRARIES.sort(key=lambda lib: lib[0])
35413548
settings.SYSTEM_JS_LIBRARIES = [lib[1] for lib in settings.SYSTEM_JS_LIBRARIES]
3542-
return consumed
3549+
return new_flags
35433550

35443551

35453552
class ScriptSource:

tests/test_other.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
from runner import RunnerCore, path_from_root, is_slow_test, ensure_dir, disabled, make_executable
3636
from runner import env_modify, no_mac, no_windows, requires_native_clang, with_env_modify
3737
from runner import create_file, parameterized, NON_ZERO, node_pthreads, TEST_ROOT, test_file
38-
from runner import compiler_for, read_file, read_binary
38+
from runner import compiler_for, read_file, read_binary, EMBUILDER
3939
from tools import shared, building, utils, deps_info
4040
import jsrun
4141
import clang_native
@@ -10630,3 +10630,15 @@ def test_no_deprecated(self):
1063010630
def test_bad_export_name(self):
1063110631
err = self.expect_fail([EMCC, '-sEXPORT_NAME=foo bar', test_file('hello_world.c')])
1063210632
self.assertContained('error: EXPORT_NAME is not a valid JS identifier: `foo bar`', err)
10633+
10634+
def test_standard_library_mapping(self):
10635+
# Test the `-l` flags on the command line get mapped the correct libraries variant
10636+
self.run_process([EMBUILDER, 'build', 'libc-mt', 'libcompiler_rt-mt', 'libdlmalloc-mt'])
10637+
10638+
libs = ['-lc', '-lc_rt_wasm', '-lcompiler_rt', '-lmalloc']
10639+
err = self.run_process([EMCC, test_file('hello_world.c'), '-pthread', '-nostdlib', '-v'] + libs, stderr=PIPE).stderr
10640+
10641+
# Check the the linker was run with `-mt` variants because `-pthread` was passed.
10642+
self.assertContained(' -lc-mt ', err)
10643+
self.assertContained(' -ldlmalloc-mt ', err)
10644+
self.assertContained(' -lcompiler_rt-mt ', err)

tools/system_libs.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -490,13 +490,14 @@ def get_usable_variations(cls):
490490
491491
This returns a dictionary of simple names to Library objects.
492492
"""
493-
result = {}
494-
for subclass in cls.get_inheritance_tree():
495-
if subclass.name:
496-
library = subclass.get_default_variation()
497-
if library.can_build() and library.can_use():
498-
result[subclass.name] = library
499-
return result
493+
if not hasattr(cls, 'useable_variations'):
494+
cls.useable_variations = {}
495+
for subclass in cls.get_inheritance_tree():
496+
if subclass.name:
497+
library = subclass.get_default_variation()
498+
if library.can_build() and library.can_use():
499+
cls.useable_variations[subclass.name] = library
500+
return cls.useable_variations
500501

501502

502503
class MTLibrary(Library):

0 commit comments

Comments
 (0)