Skip to content

Commit 8c30d6d

Browse files
committed
Remove deps_info system and and the running of llvm-nm on input file. NFC
This uses a new "stub object" construct to tell the linker (wasm-ld) not only about the existence of the JS library symbols but the native symbols on which they depend (a.k.a reverse dependencies). This allows us to completely remove deps_info.py in favor of just using normal `__deps` entries in the library files. It also means we no longer need to run `llvm-nm` on the linker inputs to discover the symbols they use. Depends on: https://reviews.llvm.org/D145308 Fixes: #18875
1 parent 8fc85aa commit 8c30d6d

24 files changed

+205
-547
lines changed

ChangeLog.md

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

2121
3.1.37 (in development)
2222
-----------------------
23+
- The old reverse dependency system based on `tools/deps_info.py` has been
24+
removed and the existing `__deps` entries in JS library files can now be used
25+
to express JS-to-native dependencies. As well being more precise, and
26+
extensible via user-supplied JS libraries, this also speeds up link times
27+
since we no longer need scan linker inputs using `llvm-nm`. It also
28+
completely removes the need for the `REVERSE_DEPS` settings which has now
29+
been deprecated. (#18905)
2330

2431
3.1.36 - 04/16/23
2532
-----------------

emcc.py

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
from tools.minimal_runtime_shell import generate_minimal_runtime_html
5151
import tools.line_endings
5252
from tools import feature_matrix
53-
from tools import deps_info
5453
from tools import js_manipulation
5554
from tools import wasm2c
5655
from tools import webassembly
@@ -1275,7 +1274,7 @@ def run(args):
12751274
process_libraries(state, [])
12761275
if len(input_files) != 1:
12771276
exit_with_error('--post-link requires a single input file')
1278-
phase_post_link(options, state, input_files[0][1], wasm_target, target)
1277+
phase_post_link(options, state, input_files[0][1], wasm_target, target, {})
12791278
return 0
12801279

12811280
## Compile source code to object files
@@ -1316,11 +1315,10 @@ def run(args):
13161315
js_info = get_js_sym_info()
13171316
if not settings.SIDE_MODULE:
13181317
js_syms = js_info['deps']
1319-
deps_info.append_deps_info(js_syms)
13201318
if settings.ASYNCIFY:
13211319
settings.ASYNCIFY_IMPORTS += ['env.' + x for x in js_info['asyncFuncs']]
13221320

1323-
phase_calculate_system_libraries(state, linker_arguments, linker_inputs, newargs)
1321+
phase_calculate_system_libraries(state, linker_arguments, newargs)
13241322

13251323
phase_link(linker_arguments, wasm_target, js_syms)
13261324

@@ -1336,7 +1334,7 @@ def run(args):
13361334

13371335
# Perform post-link steps (unless we are running bare mode)
13381336
if options.oformat != OFormat.BARE:
1339-
phase_post_link(options, state, wasm_target, wasm_target, target)
1337+
phase_post_link(options, state, wasm_target, wasm_target, target, js_syms)
13401338

13411339
return 0
13421340

@@ -1952,6 +1950,12 @@ def phase_linker_setup(options, state, newargs):
19521950

19531951
if '_main' in settings.EXPORTED_FUNCTIONS:
19541952
settings.EXPORT_IF_DEFINED.append('__main_argc_argv')
1953+
elif settings.ASSERTIONS:
1954+
# In debug builds when `main` is not explictly requested as an
1955+
# export we still add it to EXPORT_IF_DEFINED so that we can warn
1956+
# users who forget to explicitly export `main`.
1957+
# See other.test_warn_unexported_main.
1958+
settings.EXPORT_IF_DEFINED.append('main')
19551959

19561960
if settings.ASSERTIONS:
19571961
# Exceptions are thrown with a stack trace by default when ASSERTIONS is
@@ -2082,11 +2086,6 @@ def phase_linker_setup(options, state, newargs):
20822086
settings.INCLUDE_FULL_LIBRARY = 1
20832087
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$loadDylibs']
20842088

2085-
# If we are including the entire JS library then we know for sure we will, by definition,
2086-
# require all the reverse dependencies.
2087-
if settings.INCLUDE_FULL_LIBRARY:
2088-
default_setting('REVERSE_DEPS', 'all')
2089-
20902089
if settings.MAIN_MODULE == 1 or settings.SIDE_MODULE == 1:
20912090
settings.LINKABLE = 1
20922091

@@ -3086,14 +3085,13 @@ def compile_source_file(i, input_file):
30863085

30873086

30883087
@ToolchainProfiler.profile_block('calculate system libraries')
3089-
def phase_calculate_system_libraries(state, linker_arguments, linker_inputs, newargs):
3088+
def phase_calculate_system_libraries(state, linker_arguments, newargs):
30903089
extra_files_to_link = []
30913090
# Link in ports and system libraries, if necessary
30923091
if not settings.SIDE_MODULE:
30933092
# Ports are always linked into the main module, never the side module.
30943093
extra_files_to_link += ports.get_libs(settings)
3095-
all_linker_inputs = [f for _, f in sorted(linker_inputs)] + extra_files_to_link
3096-
extra_files_to_link += system_libs.calculate(all_linker_inputs, newargs, forced=state.forced_stdlibs)
3094+
extra_files_to_link += system_libs.calculate(newargs, forced=state.forced_stdlibs)
30973095
linker_arguments.extend(extra_files_to_link)
30983096

30993097

@@ -3112,7 +3110,7 @@ def phase_link(linker_arguments, wasm_target, js_syms):
31123110

31133111

31143112
@ToolchainProfiler.profile_block('post_link')
3115-
def phase_post_link(options, state, in_wasm, wasm_target, target):
3113+
def phase_post_link(options, state, in_wasm, wasm_target, target, js_syms):
31163114
global final_js
31173115

31183116
target_basename = unsuffixed_basename(target)
@@ -3134,7 +3132,7 @@ def phase_post_link(options, state, in_wasm, wasm_target, target):
31343132
else:
31353133
memfile = shared.replace_or_append_suffix(target, '.mem')
31363134

3137-
phase_emscript(options, in_wasm, wasm_target, memfile)
3135+
phase_emscript(options, in_wasm, wasm_target, memfile, js_syms)
31383136

31393137
if options.js_transform:
31403138
phase_source_transforms(options)
@@ -3152,7 +3150,7 @@ def phase_post_link(options, state, in_wasm, wasm_target, target):
31523150

31533151

31543152
@ToolchainProfiler.profile_block('emscript')
3155-
def phase_emscript(options, in_wasm, wasm_target, memfile):
3153+
def phase_emscript(options, in_wasm, wasm_target, memfile, js_syms):
31563154
# Emscripten
31573155
logger.debug('emscript')
31583156

@@ -3161,7 +3159,7 @@ def phase_emscript(options, in_wasm, wasm_target, memfile):
31613159
# _read in shell.js depends on intArrayToString when SUPPORT_BASE64_EMBEDDING is set
31623160
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$intArrayToString')
31633161

3164-
emscripten.run(in_wasm, wasm_target, final_js, memfile)
3162+
emscripten.run(in_wasm, wasm_target, final_js, memfile, js_syms)
31653163
save_intermediate('original')
31663164

31673165

emscripten.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ def create_named_globals(metadata):
296296
return '\n'.join(named_globals)
297297

298298

299-
def emscript(in_wasm, out_wasm, outfile_js, memfile):
299+
def emscript(in_wasm, out_wasm, outfile_js, memfile, js_syms):
300300
# Overview:
301301
# * Run wasm-emscripten-finalize to extract metadata and modify the binary
302302
# to use emscripten's wasm<->JS ABI
@@ -309,7 +309,7 @@ def emscript(in_wasm, out_wasm, outfile_js, memfile):
309309
# set file locations, so that JS glue can find what it needs
310310
settings.WASM_BINARY_FILE = js_manipulation.escape_for_js_string(os.path.basename(out_wasm))
311311

312-
metadata = finalize_wasm(in_wasm, out_wasm, memfile)
312+
metadata = finalize_wasm(in_wasm, out_wasm, memfile, js_syms)
313313

314314
if settings.RELOCATABLE and settings.MEMORY64 == 2:
315315
metadata.imports += ['__memory_base32']
@@ -461,7 +461,7 @@ def get_metadata(infile, outfile, modify_wasm, args):
461461
return metadata
462462

463463

464-
def finalize_wasm(infile, outfile, memfile):
464+
def finalize_wasm(infile, outfile, memfile, js_syms):
465465
building.save_intermediate(infile, 'base.wasm')
466466
args = []
467467

@@ -536,13 +536,23 @@ def finalize_wasm(infile, outfile, memfile):
536536

537537
expected_exports = set(settings.EXPORTED_FUNCTIONS)
538538
expected_exports.update(asmjs_mangle(s) for s in settings.REQUIRED_EXPORTS)
539-
540-
# Calculate the subset of exports that were explicitly marked with llvm.used.
539+
# Assume that when JS symbol dependencies are exported it is because they
540+
# are needed by by a JS symbol and are not being explicitly exported due
541+
# to EMSCRIPTEN_KEEPALIVE (llvm.used).
542+
for deps in js_syms.values():
543+
expected_exports.update(asmjs_mangle(s) for s in deps)
544+
545+
# Calculate the subset of exports that were explicitly marked as
546+
# EMSCRIPTEN_KEEPALIVE (llvm.used).
541547
# These are any exports that were not requested on the command line and are
542548
# not known auto-generated system functions.
543549
unexpected_exports = [e for e in metadata.exports if treat_as_user_function(e)]
544550
unexpected_exports = [asmjs_mangle(e) for e in unexpected_exports]
545551
unexpected_exports = [e for e in unexpected_exports if e not in expected_exports]
552+
if '_main' in unexpected_exports:
553+
logger.warning('main() is in the input files, but "_main" is not in EXPORTED_FUNCTIONS, which means it may be eliminated as dead code. Export it if you want main() to run.')
554+
unexpected_exports.remove('_main')
555+
546556
building.user_requested_exports.update(unexpected_exports)
547557
settings.EXPORTED_FUNCTIONS.extend(unexpected_exports)
548558

@@ -922,5 +932,5 @@ def normalize_line_endings(text):
922932
return text
923933

924934

925-
def run(in_wasm, out_wasm, outfile_js, memfile):
926-
emscript(in_wasm, out_wasm, outfile_js, memfile)
935+
def run(in_wasm, out_wasm, outfile_js, memfile, js_syms):
936+
emscript(in_wasm, out_wasm, outfile_js, memfile, js_syms)

site/source/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.rst

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -596,9 +596,6 @@ See the `library_*.js`_ files for other examples.
596596
This is useful when all the implemented methods use a JavaScript
597597
singleton containing helper methods. See ``library_webgl.js`` for
598598
an example.
599-
- If a JavaScript library depends on a compiled C library (like most
600-
of *libc*), you must edit `src/deps_info.json`_. Search for
601-
"deps_info" in `tools/system_libs.py`_.
602599
- The keys passed into `mergeInto` generate functions that are prefixed
603600
by ``_``. In other words ``my_func: function() {},`` becomes
604601
``function _my_func() {}``, as all C methods in emscripten have a ``_`` prefix. Keys starting with ``$`` have the ``$``
@@ -810,7 +807,6 @@ you can give it a try. See `Emnapi documentation`_ for more details.
810807

811808
.. _library.js: https://github.com/emscripten-core/emscripten/blob/main/src/library.js
812809
.. _test_js_libraries: https://github.com/emscripten-core/emscripten/blob/1.29.12/tests/test_core.py#L5043
813-
.. _src/deps_info.json: https://github.com/emscripten-core/emscripten/blob/main/src/deps_info.json
814810
.. _tools/system_libs.py: https://github.com/emscripten-core/emscripten/blob/main/tools/system_libs.py
815811
.. _library_\*.js: https://github.com/emscripten-core/emscripten/tree/main/src
816812
.. _test_add_function in test/test_core.py: https://github.com/emscripten-core/emscripten/blob/1.29.12/tests/test_core.py#L6237

src/jsifier.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,26 @@ function isDefined(symName) {
6666
return false;
6767
}
6868

69+
function getTransitiveDeps(symbol) {
70+
// TODO(sbc): Use some kind of cache to avoid quadratic behaviour here.
71+
const transitiveDeps = new Set();
72+
const seen = new Set();
73+
const toVisit = [symbol];
74+
while (toVisit.length) {
75+
const sym = toVisit.pop();
76+
if (!seen.has(sym)) {
77+
let directDeps = LibraryManager.library[sym + '__deps'] || [];
78+
directDeps = directDeps.filter((d) => typeof d === 'string');
79+
if (directDeps.length) {
80+
directDeps.forEach(transitiveDeps.add, transitiveDeps);
81+
toVisit.push(...directDeps);
82+
}
83+
seen.add(sym);
84+
}
85+
}
86+
return Array.from(transitiveDeps);
87+
}
88+
6989
function runJSify() {
7090
const libraryItems = [];
7191
const symbolDeps = {};
@@ -260,8 +280,14 @@ function ${name}(${args}) {
260280

261281
if (symbolsOnly) {
262282
if (!isJsOnlySymbol(symbol) && LibraryManager.library.hasOwnProperty(symbol)) {
263-
externalDeps = deps.filter((d) => !isJsOnlySymbol(d) && !(d in LibraryManager.library) && typeof d === 'string');
264-
symbolDeps[symbol] = externalDeps;
283+
var value = LibraryManager.library[symbol];
284+
var resolvedSymbol = symbol;
285+
// Resolve aliases before looking up deps
286+
if (typeof value == 'string' && value[0] != '=' && LibraryManager.library.hasOwnProperty(value)) {
287+
resolvedSymbol = value;
288+
}
289+
var transtiveDeps = getTransitiveDeps(resolvedSymbol);
290+
symbolDeps[symbol] = transtiveDeps.filter((d) => !isJsOnlySymbol(d) && !(d in LibraryManager.library));
265291
}
266292
return;
267293
}

src/library.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,13 @@ mergeInto(LibraryManager.library, {
12121212
// ==========================================================================
12131213

12141214
#if SUPPORT_LONGJMP == 'emscripten'
1215+
// In WebAssemblyLowerEmscriptenEHSjLj pass in the LLVM backend, function
1216+
// calls that exist in the same function with setjmp are converted to a code
1217+
// sequence that includes invokes, malloc, free, saveSetjmp, and
1218+
// emscripten_longjmp. setThrew is called from invokes, but we don't have
1219+
// any way to express that dependency so we use emscripten_throw_longjmp as
1220+
// a proxy and declare the dependency here.
1221+
_emscripten_throw_longjmp__deps: ['setThrew'],
12151222
_emscripten_throw_longjmp: function() {
12161223
#if EXCEPTION_STACK_TRACES
12171224
throw new EmscriptenSjLj;
@@ -1721,7 +1728,7 @@ mergeInto(LibraryManager.library, {
17211728
return { family: family, addr: addr, port: port };
17221729
},
17231730
$writeSockaddr__docs: '/** @param {number=} addrlen */',
1724-
$writeSockaddr__deps: ['$Sockets', '$inetPton4', '$inetPton6', '$zeroMemory'],
1731+
$writeSockaddr__deps: ['$Sockets', '$inetPton4', '$inetPton6', '$zeroMemory', 'htons'],
17251732
$writeSockaddr: function (sa, family, addr, port, addrlen) {
17261733
switch (family) {
17271734
case {{{ cDefs.AF_INET }}}:
@@ -1858,7 +1865,7 @@ mergeInto(LibraryManager.library, {
18581865
return 0;
18591866
},
18601867

1861-
getaddrinfo__deps: ['$Sockets', '$DNS', '$inetPton4', '$inetNtop4', '$inetPton6', '$inetNtop6', '$writeSockaddr'],
1868+
getaddrinfo__deps: ['$Sockets', '$DNS', '$inetPton4', '$inetNtop4', '$inetPton6', '$inetNtop6', '$writeSockaddr', 'malloc', 'htonl'],
18621869
getaddrinfo__proxy: 'sync',
18631870
getaddrinfo: function(node, service, hint, out) {
18641871
// Note getaddrinfo currently only returns a single addrinfo with ai_next defaulting to NULL. When NULL

src/library_async.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ mergeInto(LibraryManager.library, {
2222
#if ASYNCIFY
2323
$Asyncify__deps: ['$runAndAbortIfError', '$callUserCallback', '$sigToWasmTypes',
2424
#if !MINIMAL_RUNTIME
25-
'$runtimeKeepalivePush', '$runtimeKeepalivePop'
25+
'$runtimeKeepalivePush', '$runtimeKeepalivePop',
26+
#endif
27+
#if ASYNCIFY == 1
28+
// Needed by allocateData and handleSleep respectively
29+
'malloc', 'free',
2630
#endif
2731
],
2832

src/library_browser.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,7 @@ var LibraryBrowser = {
12121212

12131213
// To avoid creating worker parent->child chains, always proxies to execute on the main thread.
12141214
emscripten_create_worker__proxy: 'sync',
1215+
emscripten_create_worker__deps: ['$UTF8ToString', 'malloc', 'free'],
12151216
emscripten_create_worker: function(url) {
12161217
url = UTF8ToString(url);
12171218
var id = Browser.workers.length;
@@ -1253,6 +1254,7 @@ var LibraryBrowser = {
12531254
return id;
12541255
},
12551256

1257+
emscripten_destroy_worker__deps: ['free'],
12561258
emscripten_destroy_worker__proxy: 'sync',
12571259
emscripten_destroy_worker: function(id) {
12581260
var info = Browser.workers[id];

src/library_exceptions.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@ var LibraryExceptions = {
2323
//
2424
// excPtr - Thrown object pointer to wrap. Metadata pointer is calculated from it.
2525
$ExceptionInfo__docs: '/** @constructor */',
26+
$ExceptionInfo__deps: [
27+
'__cxa_is_pointer_type',
2628
#if EXCEPTION_DEBUG
27-
$ExceptionInfo__deps: ['$ptrToString'],
29+
'$ptrToString'
2830
#endif
31+
],
2932
$ExceptionInfo: function(excPtr) {
3033
this.excPtr = excPtr;
3134
this.ptr = excPtr - {{{ C_STRUCTS.__cxa_exception.__size__ }}};
@@ -416,6 +419,13 @@ var LibraryExceptions = {
416419
addCxaCatch = function(n) {
417420
LibraryManager.library['__cxa_find_matching_catch_' + n] = '__cxa_find_matching_catch';
418421
};
422+
423+
// Add the first 10 catch handlers premptively. Others get added on demand in
424+
// jsifier. This is done here primarily so that these symbols end up with the
425+
// correct deps in the stub library that we pass to wasm-ld.
426+
for (let i = 1; i < 10; i++) {
427+
addCxaCatch(i)
428+
}
419429
#endif
420430

421431
mergeInto(LibraryManager.library, LibraryExceptions);

src/library_exceptions_stub.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ var LibraryExceptions = {};
2424
#if !INCLUDE_FULL_LIBRARY
2525
// This method of link-time error genertation is not compatible with INCLUDE_FULL_LIBRARY
2626
LibraryExceptions[name + '__deps'] = [function() {
27-
error('DISABLE_EXCEPTION_THROWING was set (likely due to -fno-exceptions), which means no C++ exception throwing support code is linked in, but such support is required by symbol ' + name + '. Either do not set DISABLE_EXCEPTION_THROWING (if you do want exception throwing) or compile all source files with -fno-except (so that no exceptions support code is required); also make sure DISABLE_EXCEPTION_CATCHING is set to the right value - if you want exceptions, it should be off, and vice versa.');
27+
error(`DISABLE_EXCEPTION_THROWING was set (likely due to -fno-exceptions), which means no C++ exception throwing support code is linked in, but such support is required by symbol '${name}'. Either do not set DISABLE_EXCEPTION_THROWING (if you do want exception throwing) or compile all source files with -fno-except (so that no exceptions support code is required); also make sure DISABLE_EXCEPTION_CATCHING is set to the right value - if you want exceptions, it should be off, and vice versa.`);
2828
}];
2929
#endif
3030
});

src/library_glew.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
*/
2121

2222
var LibraryGLEW = {
23-
$GLEW__deps: ['glGetString', '$stringToNewUTF8'],
23+
$GLEW__deps: ['glGetString', '$stringToNewUTF8', '$UTF8ToString'],
2424
$GLEW: {
2525
isLinaroFork: 1,
2626
extensions: null,

src/library_glfw.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1123,7 +1123,7 @@ var LibraryGLFW = {
11231123
/*******************************************************************************
11241124
* GLFW FUNCTIONS
11251125
******************************************************************************/
1126-
glfwInit__deps: ['emscripten_get_device_pixel_ratio'],
1126+
glfwInit__deps: ['emscripten_get_device_pixel_ratio', 'malloc', 'free'],
11271127
glfwInit__sig: 'i',
11281128
glfwInit: function() {
11291129
if (GLFW.windows) return 1; // GL_TRUE

0 commit comments

Comments
 (0)