Skip to content

Add a new way to mark async imports in library files. #19093

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 30, 2023
Merged
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
39 changes: 15 additions & 24 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
}

DEFAULT_ASYNCIFY_IMPORTS = [
'emscripten_sleep', 'emscripten_wget', 'emscripten_wget_data', 'emscripten_idb_load',
'emscripten_wget', 'emscripten_wget_data', 'emscripten_idb_load',
'emscripten_idb_store', 'emscripten_idb_delete', 'emscripten_idb_exists',
'emscripten_idb_load_blob', 'emscripten_idb_store_blob', 'SDL_Delay',
'emscripten_scan_registers', 'emscripten_lazy_load_code',
Expand Down Expand Up @@ -507,7 +507,7 @@ def ensure_archive_index(archive_file):
run_process([shared.LLVM_RANLIB, archive_file])


def generate_js_symbols():
def generate_js_sym_info():
# Runs the js compiler to generate a list of all symbols available in the JS
# libraries. This must be done separately for each linker invokation since the
# list of symbols depends on what settings are used.
Expand All @@ -520,11 +520,11 @@ def generate_js_symbols():


@ToolchainProfiler.profile_block('JS symbol generation')
def get_all_js_syms():
def get_js_sym_info():
# Avoiding using the cache when generating struct info since
# this step is performed while the cache is locked.
if DEBUG or settings.BOOTSTRAPPING_STRUCT_INFO or config.FROZEN_CACHE:
return generate_js_symbols()
return generate_js_sym_info()

# We define a cache hit as when the settings and `--js-library` contents are
# identical.
Expand All @@ -545,29 +545,16 @@ def get_all_js_syms():
def build_symbol_list(filename):
"""Only called when there is no existing symbol list for a given content hash.
"""
library_syms = generate_js_symbols()
lines = []
library_syms = generate_js_sym_info()

for name, deps in library_syms.items():
if deps:
lines.append('%s: %s' % (name, ','.join(deps)))
else:
lines.append(name)
write_file(filename, '\n'.join(lines) + '\n')
write_file(filename, json.dumps(library_syms, separators=(',', ':')))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't quite as compact as the previous format, but JSON should be fast and it seems easier than maintaining our own serialize/deserialize now that there's more meta data.


# We need to use a separate lock here for symbol lists because, unlike with system libraries,
# it's normally for these file to get pruned as part of normal operation. This means that it
# can be deleted between the `cache.get()` then the `read_file`.
with filelock.FileLock(cache.get_path(cache.get_path('symbol_lists.lock'))):
filename = cache.get(f'symbol_lists/{content_hash}.txt', build_symbol_list)
lines = read_file(filename).splitlines()
library_syms = {}
for line in lines:
if ':' in line:
name, deps = line.split(':')
library_syms[name] = deps.strip().split(',')
else:
library_syms[line] = []
filename = cache.get(f'symbol_lists/{content_hash}.json', build_symbol_list)
library_syms = json.loads(read_file(filename))

# Limit of the overall size of the cache to 100 files.
# This code will get test coverage since a full test run of `other` or `core`
Expand Down Expand Up @@ -1334,9 +1321,13 @@ def run(args):
return 0

js_syms = {}
if not settings.SIDE_MODULE:
js_syms = get_all_js_syms()
deps_info.append_deps_info(js_syms)
if not settings.SIDE_MODULE or settings.ASYNCIFY:
js_info = get_js_sym_info()
if not settings.SIDE_MODULE:
js_syms = js_info['deps']
deps_info.append_deps_info(js_syms)
if settings.ASYNCIFY:
settings.ASYNCIFY_IMPORTS += ['env.' + x for x in js_info['asyncFuncs']]

phase_calculate_system_libraries(state, linker_arguments, linker_inputs, newargs)

Expand Down
2 changes: 1 addition & 1 deletion src/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ if (symbolsOnly) {
}

// Side modules are pure wasm and have no JS
assert(!SIDE_MODULE, 'JS compiler should not run on side modules');
assert(!SIDE_MODULE || (ASYNCIFY && global.symbolsOnly), 'JS compiler should only run on side modules if asyncify is used.');

// Output some info and warnings based on settings

Expand Down
22 changes: 21 additions & 1 deletion src/jsifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ function isDefined(symName) {
function runJSify() {
const libraryItems = [];
const symbolDeps = {};
const asyncFuncs = [];
let postSets = [];

LibraryManager.load();
Expand Down Expand Up @@ -247,6 +248,18 @@ function ${name}(${args}) {

const deps = LibraryManager.library[symbol + '__deps'] || [];

let isAsyncFunction = false;
if (ASYNCIFY) {
const original = LibraryManager.library[symbol];
if (typeof original == 'function' ) {
isAsyncFunction = LibraryManager.library[symbol + '__async'] ||
original.constructor.name == 'AsyncFunction'
}
if (isAsyncFunction) {
asyncFuncs.push(symbol);
}
}

if (symbolsOnly) {
if (!isJsOnlySymbol(symbol) && LibraryManager.library.hasOwnProperty(symbol)) {
externalDeps = deps.filter((d) => !isJsOnlySymbol(d) && !(d in LibraryManager.library) && typeof d === 'string');
Expand Down Expand Up @@ -423,6 +436,9 @@ function ${name}(${args}) {
if (sig && (RELOCATABLE || ASYNCIFY == 2)) {
contentText += `\n${mangled}.sig = '${sig}';`;
}
if (ASYNCIFY && isAsyncFunction) {
contentText += `\n${mangled}.isAsync = true;`;
}
if (isStub) {
contentText += `\n${mangled}.stub = true;`;
if (ASYNCIFY) {
Expand Down Expand Up @@ -529,6 +545,7 @@ function ${name}(${args}) {
print('//FORWARDED_DATA:' + JSON.stringify({
librarySymbols: librarySymbols,
warnings: warnings,
asyncFuncs,
ATINITS: ATINITS.join('\n'),
ATMAINS: ATMAINS.join('\n'),
ATEXITS: ATEXITS.join('\n'),
Expand All @@ -540,7 +557,10 @@ function ${name}(${args}) {
}

if (symbolsOnly) {
print(JSON.stringify(symbolDeps));
print(JSON.stringify({
deps: symbolDeps,
asyncFuncs
}));
return;
}

Expand Down
4 changes: 3 additions & 1 deletion src/library_async.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ mergeInto(LibraryManager.library, {
var original = imports[x];
var sig = original.sig;
if (typeof original == 'function') {
var isAsyncifyImport = ASYNCIFY_IMPORTS.indexOf(x) >= 0 ||
var isAsyncifyImport = original.isAsync ||
ASYNCIFY_IMPORTS.indexOf(x) >= 0 ||
x.startsWith('__asyncjs__');
#if ASYNCIFY == 2
// Wrap async imports with a suspending WebAssembly function.
Expand Down Expand Up @@ -451,6 +452,7 @@ mergeInto(LibraryManager.library, {
},

emscripten_sleep__deps: ['$safeSetTimeout'],
emscripten_sleep__async: true,
emscripten_sleep: function(ms) {
// emscripten_sleep() does not return a value, but we still need a |return|
// here for stack switching support (ASYNCIFY=2). In that mode this function
Expand Down
1 change: 1 addition & 0 deletions src/utility.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ function isJsLibraryConfigIdentifier(ident) {
'__noleakcheck',
'__internal',
'__user',
'__async',
];
return suffixes.some((suffix) => ident.endsWith(suffix));
}
Expand Down
2 changes: 1 addition & 1 deletion tools/gen_sig_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ def extract_sig_info(sig_info, extra_settings=None, extra_cflags=None):
output = shared.run_js_tool(utils.path_from_root('src/compiler.js'),
['--symbols-only', settings_json],
stdout=subprocess.PIPE, cwd=utils.path_from_root())
symbols = json.loads(output).keys()
symbols = json.loads(output)['deps'].keys()
symbols = [s for s in symbols if not ignore_symbol(s)]
with tempfiles.get_file('.c') as c_file:
create_c_file(c_file, symbols)
Expand Down