Skip to content

Commit f11d619

Browse files
authored
Enable LLD_REPORT_UNDEFINED by default (#16003)
This makes undefined symbol errors more precise by including the name of the object that references the undefined symbol. Its also paves the way (in my mind anyway) for finally fixing reverse dependencies in a salable way. See #15982. That PR uses an alternative script for the pre-processing of dependencies but also fundamentally relies on processing JS libraries both before and after linking. The cost is about 300ms per link operation due to double processing of the JS libraries, but results are cached so in practice this only happens the first time a given link command is run (see #18326).
1 parent 48ace16 commit f11d619

File tree

6 files changed

+44
-47
lines changed

6 files changed

+44
-47
lines changed

ChangeLog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ See docs/process.md for more on how version tagging works.
2020

2121
3.1.28 (in development)
2222
-----------------------
23+
- `LLD_REPORT_UNDEFINED` is now enabled by default. This makes undefined symbol
24+
errors more precise by including the name of the object that references the
25+
undefined symbol. The old behaviour (of allowing all undefined symbols at
26+
wasm-ld time and reporting them later when processing JS library files) is
27+
still available using `-sLLD_REPORT_UNDEFINED=0`. (#16003)
2328
- musl libc updated from v1.2.2 to v1.2.3. (#18270)
2429
- The default emscripten config file no longer contains `EMSCRIPTEN_ROOT`. This
2530
setting has long been completely ignored by emscripten itself. For

emcc.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -553,9 +553,8 @@ def build_symbol_list(filename):
553553
library_syms = read_file(filename).splitlines()
554554

555555
# Limit of the overall size of the cache to 100 files.
556-
# This code will get test coverage once we make LLD_REPORT_UNDEFINED the default
557-
# since under those circumstances a full test run of `other` or `core` generates
558-
# ~1000 unique symbol lists.
556+
# This code will get test coverage since a full test run of `other` or `core`
557+
# generates ~1000 unique symbol lists.
559558
cache_limit = 100
560559
root = cache.get_path('symbol_lists')
561560
if len(os.listdir(root)) > cache_limit:
@@ -1898,10 +1897,7 @@ def phase_linker_setup(options, state, newargs):
18981897
if not settings.PURE_WASI and '-nostdlib' not in newargs and '-nodefaultlibs' not in newargs:
18991898
default_setting('STACK_OVERFLOW_CHECK', max(settings.ASSERTIONS, settings.STACK_OVERFLOW_CHECK))
19001899

1901-
if settings.LLD_REPORT_UNDEFINED or settings.STANDALONE_WASM:
1902-
# Reporting undefined symbols at wasm-ld time requires us to know if we have a `main` function
1903-
# or not, as does standalone wasm mode.
1904-
# TODO(sbc): Remove this once this becomes the default
1900+
if settings.STANDALONE_WASM:
19051901
settings.IGNORE_MISSING_MAIN = 0
19061902

19071903
# For users that opt out of WARN_ON_UNDEFINED_SYMBOLS we assume they also

src/settings.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1917,12 +1917,13 @@ var USE_OFFSET_CONVERTER = false;
19171917
// This is enabled automatically when using -g4 with sanitizers.
19181918
var LOAD_SOURCE_MAP = false;
19191919

1920-
// If set to 1, the JS compiler is run before wasm-ld so that the linker can
1921-
// report undefined symbols within the binary. Without this option the linker
1922-
// doesn't know which symbols might be defined in JS so reporting of undefined
1923-
// symbols is delayed until the JS compiler is run.
1920+
// If set to 0, delay undefined symbol report until after wasm-ld runs. This
1921+
// avoids running the the JS compiler prior to wasm-ld, but reduces the amount
1922+
// of information in the undefined symbol message (Since JS compiler cannot
1923+
// report the name of the object file that contains the reference to the
1924+
// undefined symbol).
19241925
// [link]
1925-
var LLD_REPORT_UNDEFINED = false;
1926+
var LLD_REPORT_UNDEFINED = true;
19261927

19271928
// Default to c++ mode even when run as `emcc` rather then `emc++`.
19281929
// When this is disabled `em++` is required when compiling and linking C++

test/test_core.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4149,8 +4149,8 @@ def test_dylink_basics_no_modify(self):
41494149
self.do_basic_dylink_test()
41504150

41514151
@needs_dylink
4152-
def test_dylink_basics_lld_report_undefined(self):
4153-
self.set_setting('LLD_REPORT_UNDEFINED')
4152+
def test_dylink_basics_no_lld_report_undefined(self):
4153+
self.set_setting('LLD_REPORT_UNDEFINED', 0)
41544154
self.do_basic_dylink_test()
41554155

41564156
@needs_dylink
@@ -5150,9 +5150,6 @@ def test_dylink_rtti(self):
51505150
# in the another module.
51515151
# Each module will define its own copy of certain COMDAT symbols such as
51525152
# each classs's typeinfo, but at runtime they should both use the same one.
5153-
# Use LLD_REPORT_UNDEFINED to test that it works as expected with weak/COMDAT
5154-
# symbols.
5155-
self.set_setting('LLD_REPORT_UNDEFINED')
51565153
header = '''
51575154
#include <cstddef>
51585155
@@ -6161,7 +6158,6 @@ def test_unistd_io(self):
61616158
'nodefs': (['NODEFS']),
61626159
})
61636160
def test_unistd_misc(self, fs):
6164-
self.set_setting('LLD_REPORT_UNDEFINED')
61656161
self.emcc_args += ['-D' + fs]
61666162
if fs == 'NODEFS':
61676163
self.require_node()
@@ -9416,9 +9412,8 @@ def test_undefined_main(self):
94169412
# In standalone we don't support implicitly building without main. The user has to explicitly
94179413
# opt out (see below).
94189414
err = self.expect_fail([EMCC, test_file('core/test_ctors_no_main.cpp')] + self.get_emcc_args())
9419-
self.assertContained('error: undefined symbol: main/__main_argc_argv (referenced by top-level compiled C/C++ code)', err)
9420-
self.assertContained('warning: To build in STANDALONE_WASM mode without a main(), use emcc --no-entry', err)
9421-
elif not self.get_setting('LLD_REPORT_UNDEFINED') and not self.get_setting('STRICT'):
9415+
self.assertContained('undefined symbol: main', err)
9416+
elif not self.get_setting('STRICT'):
94229417
# Traditionally in emscripten we allow main to be implicitly undefined. This allows programs
94239418
# with a main and libraries without a main to be compiled identically.
94249419
# However we are trying to move away from that model to a more explicit opt-out model. See:
@@ -9436,6 +9431,9 @@ def test_undefined_main(self):
94369431
self.do_core_test('test_ctors_no_main.cpp')
94379432
self.clear_setting('EXPORTED_FUNCTIONS')
94389433

9434+
# Marked as impure since the WASI reactor modules (modules without main)
9435+
# are not yet suppored by the wasm engines we test against.
9436+
@also_with_standalone_wasm(impure=True)
94399437
def test_undefined_main_explict(self):
94409438
# If we pass --no-entry this test should compile without issue
94419439
self.emcc_args.append('--no-entry')
@@ -9708,7 +9706,6 @@ def setUp(self):
97089706
settings={'ALLOW_MEMORY_GROWTH': 1})
97099707

97109708
# Experimental modes (not tested by CI)
9711-
lld = make_run('lld', emcc_args=[], settings={'LLD_REPORT_UNDEFINED': 1})
97129709
minimal0 = make_run('minimal0', emcc_args=['-g'], settings={'MINIMAL_RUNTIME': 1})
97139710

97149711
# TestCoreBase is just a shape for the specific subclasses, we don't test it itself

test/test_other.py

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2167,8 +2167,8 @@ def test_undefined_symbols(self, action):
21672167
print(proc.stderr)
21682168
if value or action is None:
21692169
# The default is that we error in undefined symbols
2170-
self.assertContained('error: undefined symbol: something', proc.stderr)
2171-
self.assertContained('error: undefined symbol: elsey', proc.stderr)
2170+
self.assertContained('undefined symbol: something', proc.stderr)
2171+
self.assertContained('undefined symbol: elsey', proc.stderr)
21722172
check_success = False
21732173
elif action == 'ERROR' and not value:
21742174
# Error disables, should only warn
@@ -3548,7 +3548,7 @@ def test_js_lib_missing_sig(self):
35483548
def test_js_lib_quoted_key(self):
35493549
create_file('lib.js', r'''
35503550
mergeInto(LibraryManager.library, {
3551-
__internal_data:{
3551+
__internal_data:{
35523552
'<' : 0,
35533553
'white space' : 1
35543554
},
@@ -6591,7 +6591,7 @@ def test_no_warn_exported_jslibfunc(self):
65916591
err = self.expect_fail([EMCC, test_file('hello_world.c'),
65926592
'-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=alGetError',
65936593
'-sEXPORTED_FUNCTIONS=_main,_alGet'])
6594-
self.assertContained('undefined exported symbol: "_alGet"', err)
6594+
self.assertContained('error: undefined exported symbol: "_alGet" [-Wundefined] [-Werror]', err)
65956595

65966596
def test_musl_syscalls(self):
65976597
self.run_process([EMCC, test_file('hello_world.c')])
@@ -8361,7 +8361,7 @@ def test_full_js_library(self):
83618361
def test_full_js_library_undefined(self):
83628362
create_file('main.c', 'void foo(); int main() { foo(); return 0; }')
83638363
err = self.expect_fail([EMCC, 'main.c', '-sSTRICT_JS', '-sINCLUDE_FULL_LIBRARY'])
8364-
self.assertContained('error: undefined symbol: foo', err)
8364+
self.assertContained('undefined symbol: foo', err)
83658365

83668366
def test_full_js_library_except(self):
83678367
self.set_setting('INCLUDE_FULL_LIBRARY', 1)
@@ -9017,19 +9017,20 @@ def test_js_preprocess(self):
90179017

90189018
err = self.run_process([EMCC, test_file('hello_world.c'), '--js-library', 'lib.js'], stderr=PIPE).stderr
90199019
self.assertContained('JSLIB: none of the above', err)
9020-
self.assertEqual(err.count('JSLIB'), 1)
9020+
self.assertNotContained('JSLIB: MAIN_MODULE', err)
9021+
self.assertNotContained('JSLIB: EXIT_RUNTIME', err)
90219022

90229023
err = self.run_process([EMCC, test_file('hello_world.c'), '--js-library', 'lib.js', '-sMAIN_MODULE'], stderr=PIPE).stderr
90239024
self.assertContained('JSLIB: MAIN_MODULE=1', err)
9024-
self.assertEqual(err.count('JSLIB'), 1)
9025+
self.assertNotContained('JSLIB: EXIT_RUNTIME', err)
90259026

90269027
err = self.run_process([EMCC, test_file('hello_world.c'), '--js-library', 'lib.js', '-sMAIN_MODULE=2'], stderr=PIPE).stderr
90279028
self.assertContained('JSLIB: MAIN_MODULE=2', err)
9028-
self.assertEqual(err.count('JSLIB'), 1)
9029+
self.assertNotContained('JSLIB: EXIT_RUNTIME', err)
90299030

90309031
err = self.run_process([EMCC, test_file('hello_world.c'), '--js-library', 'lib.js', '-sEXIT_RUNTIME'], stderr=PIPE).stderr
90319032
self.assertContained('JSLIB: EXIT_RUNTIME', err)
9032-
self.assertEqual(err.count('JSLIB'), 1)
9033+
self.assertNotContained('JSLIB: MAIN_MODULE', err)
90339034

90349035
def test_html_preprocess(self):
90359036
src_file = test_file('module/test_stdin.c')
@@ -9202,7 +9203,7 @@ def test_dash_s_list_parsing(self):
92029203
# stray slash
92039204
('EXPORTED_FUNCTIONS=["_a", "_b",\\ "_c", "_d"]', 'undefined exported symbol: "\\\\ "_c"'),
92049205
# missing comma
9205-
('EXPORTED_FUNCTIONS=["_a", "_b" "_c", "_d"]', 'undefined exported symbol: "_b" "_c"'),
9206+
('EXPORTED_FUNCTIONS=["_a", "_b" "_c", "_d"]', 'emcc: error: undefined exported symbol: "_b" "_c" [-Wundefined] [-Werror]'),
92069207
]:
92079208
print(export_arg)
92089209
proc = self.run_process([EMCC, 'src.c', '-s', export_arg], stdout=PIPE, stderr=PIPE, check=not expected)
@@ -10886,20 +10887,20 @@ def test_signature_mismatch(self):
1088610887
self.expect_fail([EMCC, '-Wl,--fatal-warnings', 'a.c', 'b.c'])
1088710888
self.expect_fail([EMCC, '-sSTRICT', 'a.c', 'b.c'])
1088810889

10890+
# TODO(sbc): Remove these tests once we remove the LLD_REPORT_UNDEFINED
1088910891
def test_lld_report_undefined(self):
1089010892
create_file('main.c', 'void foo(); int main() { foo(); return 0; }')
10891-
stderr = self.expect_fail([EMCC, '-sLLD_REPORT_UNDEFINED', 'main.c'])
10892-
self.assertContained('wasm-ld: error:', stderr)
10893-
self.assertContained('main_0.o: undefined symbol: foo', stderr)
10893+
stderr = self.expect_fail([EMCC, '-sLLD_REPORT_UNDEFINED=0', 'main.c'])
10894+
self.assertContained('error: undefined symbol: foo (referenced by top-level compiled C/C++ code)', stderr)
1089410895

1089510896
def test_lld_report_undefined_reverse_deps(self):
10896-
self.run_process([EMCC, '-sLLD_REPORT_UNDEFINED', '-sREVERSE_DEPS=all', test_file('hello_world.c')])
10897+
self.run_process([EMCC, '-sLLD_REPORT_UNDEFINED=0', '-sREVERSE_DEPS=all', test_file('hello_world.c')])
1089710898

1089810899
def test_lld_report_undefined_exceptions(self):
10899-
self.run_process([EMXX, '-sLLD_REPORT_UNDEFINED', '-fwasm-exceptions', test_file('hello_libcxx.cpp')])
10900+
self.run_process([EMXX, '-sLLD_REPORT_UNDEFINED=0', '-fwasm-exceptions', test_file('hello_libcxx.cpp')])
1090010901

1090110902
def test_lld_report_undefined_main_module(self):
10902-
self.run_process([EMCC, '-sLLD_REPORT_UNDEFINED', '-sMAIN_MODULE=2', test_file('hello_world.c')])
10903+
self.run_process([EMCC, '-sLLD_REPORT_UNDEFINED=0', '-sMAIN_MODULE=2', test_file('hello_world.c')])
1090310904

1090410905
# Verifies that warning messages that Closure outputs are recorded to console
1090510906
def test_closure_warnings(self):
@@ -11037,14 +11038,12 @@ def test_linker_version(self):
1103711038
def test_chained_js_error_diagnostics(self):
1103811039
err = self.expect_fail([EMCC, test_file('test_chained_js_error_diagnostics.c'), '--js-library', test_file('test_chained_js_error_diagnostics.js')])
1103911040
self.assertContained("error: undefined symbol: nonexistent_function (referenced by bar__deps: ['nonexistent_function'], referenced by foo__deps: ['bar'], referenced by top-level compiled C/C++ code)", err)
11040-
# Check that we don't recommend LLD_REPORT_UNDEFINED for chained dependencies.
11041-
self.assertNotContained('LLD_REPORT_UNDEFINED', err)
1104211041

11043-
# Test without chaining. In this case we don't include the JS library at all resulting in `foo`
11044-
# being undefined in the native code and in this case we recommend LLD_REPORT_UNDEFINED.
11042+
# Test without chaining. In this case we don't include the JS library at
11043+
# all resulting in `foo` being undefined in the native code.
1104511044
err = self.expect_fail([EMCC, test_file('test_chained_js_error_diagnostics.c')])
11046-
self.assertContained('error: undefined symbol: foo (referenced by top-level compiled C/C++ code)', err)
11047-
self.assertContained('Link with `-sLLD_REPORT_UNDEFINED` to get more information on undefined symbols', err)
11045+
self.assertContained('undefined symbol: foo', err)
11046+
self.assertNotContained('referenced by top-level compiled C/C++ code', err)
1104811047

1104911048
def test_xclang_flag(self):
1105011049
create_file('foo.h', ' ')
@@ -11846,7 +11845,7 @@ def test_no_main_with_PROXY_TO_PTHREAD(self):
1184611845
void foo() {}
1184711846
''')
1184811847
err = self.expect_fail([EMCC, 'lib.cpp', '-pthread', '-sPROXY_TO_PTHREAD'])
11849-
self.assertContained('error: PROXY_TO_PTHREAD proxies main() for you, but no main exists', err)
11848+
self.assertContained('crt1_proxy_main.o: undefined symbol: main', err)
1185011849

1185111850
def test_archive_bad_extension(self):
1185211851
# Regression test for https://github.com/emscripten-core/emscripten/issues/14012
@@ -11888,7 +11887,7 @@ def test_unimplemented_syscalls(self, args):
1188811887
cmd = [EMCC, 'main.c', '-sASSERTIONS'] + args
1188911888
if args:
1189011889
err = self.expect_fail(cmd)
11891-
self.assertContained('error: undefined symbol: __syscall_mincore', err)
11890+
self.assertContained('libc-debug.a(mincore.o): undefined symbol: __syscall_mincore', err)
1189211891
else:
1189311892
self.run_process(cmd)
1189411893
err = self.run_js('a.out.js')

tools/gen_struct_info.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,6 @@ def inspect_headers(headers, cflags):
269269
'-nostdlib',
270270
compiler_rt,
271271
'-sBOOTSTRAPPING_STRUCT_INFO',
272-
'-sLLD_REPORT_UNDEFINED',
273272
'-sSTRICT',
274273
'-sASSERTIONS=0',
275274
# Use SINGLE_FILE so there is only a single

0 commit comments

Comments
 (0)