Skip to content

Move PROXY_TO_PTHREAD handling into native code. NFC #17153

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 1 commit into from
Jun 10, 2022
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
11 changes: 4 additions & 7 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1726,7 +1726,10 @@ def phase_linker_setup(options, state, newargs, user_settings):
settings.EXPECT_MAIN = 0
else:
assert not settings.EXPORTED_FUNCTIONS
settings.EXPORTED_FUNCTIONS = ['_main']
# With PROXY_TO_PTHREAD we don't export `main` at all but instead
# we export `emscripten_proxy_main` (elsewhwere).
if not settings.PROXY_TO_PTHREAD:
settings.EXPORTED_FUNCTIONS = ['_main']

if settings.STANDALONE_WASM:
# In STANDALONE_WASM mode we either build a command or a reactor.
Expand Down Expand Up @@ -2504,12 +2507,6 @@ def check_memory_setting(setting):
# they are indirect calls, since that is what they do - we can't see their
# targets statically.
settings.ASYNCIFY_IMPORTS += ['invoke_*']
# with pthreads we may call main through the __call_main mechanism, which can
# therefore reach anything in the program, so mark it as possibly causing a
# sleep (the asyncify analysis doesn't look through JS, just wasm, so it can't
# see what it itself calls)
if settings.USE_PTHREADS:
settings.ASYNCIFY_IMPORTS += ['__call_main']
# add the default imports
settings.ASYNCIFY_IMPORTS += DEFAULT_ASYNCIFY_IMPORTS

Expand Down
8 changes: 1 addition & 7 deletions emscripten.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def update_settings_glue(wasm_file, metadata):
if settings.MEMORY64:
assert '--enable-memory64' in settings.BINARYEN_FEATURES

settings.HAS_MAIN = bool(settings.MAIN_MODULE) or settings.STANDALONE_WASM or 'main' in settings.WASM_EXPORTS or '__main_argc_argv' in settings.WASM_EXPORTS
settings.HAS_MAIN = bool(settings.MAIN_MODULE) or settings.PROXY_TO_PTHREAD or settings.STANDALONE_WASM or 'main' in settings.WASM_EXPORTS or '__main_argc_argv' in settings.WASM_EXPORTS

# When using dynamic linking the main function might be in a side module.
# To be safe assume they do take input parametes.
Expand Down Expand Up @@ -220,12 +220,6 @@ def report_missing_symbols(js_library_funcs):
# In this mode we never expect _main in the export list.
return

# PROXY_TO_PTHREAD only makes sense with a main(), so error if one is
# missing. note that when main() might arrive from another module we cannot
# error here.
if settings.PROXY_TO_PTHREAD and not settings.HAS_MAIN:
exit_with_error('PROXY_TO_PTHREAD proxies main() for you, but no main exists')

if settings.IGNORE_MISSING_MAIN:
# The default mode for emscripten is to ignore the missing main function allowing
# maximum compatibility.
Expand Down
14 changes: 12 additions & 2 deletions src/jsifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ global.proxiedFunctionTable = ['null'/* Reserve index 0 for an undefined functio

// Mangles the given C/JS side function name to assembly level function name (adds an underscore)
function mangleCSymbolName(f) {
if (f === '__main_argc_argv') {
f = 'main';
}
return f[0] == '$' ? f.substr(1) : '_' + f;
}

Expand Down Expand Up @@ -53,6 +56,9 @@ function isDefined(symName) {
if (WASM_EXPORTS.has(symName) || SIDE_MODULE_EXPORTS.has(symName)) {
return true;
}
if (symName == '__main_argc_argv' && SIDE_MODULE_EXPORTS.has('main')) {
return true;
}
// 'invoke_' symbols are created at runtime in libary_dylink.py so can
// always be considered as defined.
if (RELOCATABLE && symName.startsWith('invoke_')) {
Expand Down Expand Up @@ -208,9 +214,13 @@ function ${name}(${args}) {
}
const isWeakImport = WEAK_IMPORTS.has(ident);
if (!isDefined(ident) && !isWeakImport) {
if (PROXY_TO_PTHREAD && !MAIN_MODULE && ident == '__main_argc_argv') {
error('PROXY_TO_PTHREAD proxies main() for you, but no main exists');
return;
}
let undefinedSym = ident;
if (ident === '__main_argc_argv') {
undefinedSym = 'main';
undefinedSym = 'main/__main_argc_argv';
}
let msg = 'undefined symbol: ' + undefinedSym;
if (dependent) msg += ` (referenced by ${dependent})`;
Expand All @@ -224,7 +234,7 @@ function ${name}(${args}) {
} else if (VERBOSE || WARN_ON_UNDEFINED_SYMBOLS) {
warn(msg);
}
if (undefinedSym === 'main' && STANDALONE_WASM) {
if (ident === '__main_argc_argv' && STANDALONE_WASM) {
warn('To build in STANDALONE_WASM mode without a main(), use emcc --no-entry');
}
}
Expand Down
13 changes: 7 additions & 6 deletions src/library_dylink.js
Original file line number Diff line number Diff line change
Expand Up @@ -475,16 +475,17 @@ var LibraryDylink = {

// Export native export on the Module object.
// TODO(sbc): Do all users want this? Should we skip this by default?
#if !hasExportedFunction('_main')
// If the main module doesn't define main it could be defined in one of
// the side module, and we need to handle the mangled named.
var module_sym = asmjsMangle(sym == '__main_argc_argv' ? 'main' : sym);
#else
var module_sym = asmjsMangle(sym);
#endif
if (!Module.hasOwnProperty(module_sym)) {
Module[module_sym] = exports[sym];
}
#if !hasExportedFunction('_main')
// If the main module doesn't define main it could be defined in one of
// the side modules, and we need to handle the mangled named.
if (sym == '__main_argc_argv') {
Module['_main'] = exports[sym];
}
#endif
}
},

Expand Down
24 changes: 0 additions & 24 deletions src/library_pthread.js
Original file line number Diff line number Diff line change
Expand Up @@ -807,30 +807,6 @@ var LibraryPThread = {
return 0;
},

#if PROXY_TO_PTHREAD
__call_main__deps: ['exit', '$exitOnMainThread'],
__call_main: function(argc, argv) {
#if !EXIT_RUNTIME
// EXIT_RUNTIME==0 set, keeping main thread alive by default.
noExitRuntime = true;
#endif
var returnCode = {{{ exportedAsmFunc('_main') }}}(argc, argv);
#if EXIT_RUNTIME
if (!keepRuntimeAlive()) {
// exitRuntime enabled, proxied main() finished in a pthread, shut down the process.
#if PTHREADS_DEBUG
err('Proxied main thread finished with return code ' + returnCode + '. EXIT_RUNTIME=1 set, quitting process.');
#endif
exitOnMainThread(returnCode);
}
#else
#if PTHREADS_DEBUG
err('Proxied main thread finished with return code ' + returnCode + '. EXIT_RUNTIME=0 set, so keeping main thread alive for asynchronous event operations.');
#endif
#endif
},
#endif

// This function is call by a pthread to signal that exit() was called and
// that the entire process should exit.
// This function is always called from a pthread, but is executed on the
Expand Down
14 changes: 12 additions & 2 deletions system/lib/pthread/emscripten_proxy_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,30 @@
*/

#include <pthread.h>
#include <stdlib.h>

#include <emscripten.h>
#include <emscripten/stack.h>
#include <emscripten/threading.h>
#include <emscripten/eventloop.h>

static int _main_argc;
static char** _main_argv;

extern void __call_main(int argc, char** argv);
int __main_argc_argv(int argc, char *argv[]);

weak int __main_void(void) {
return __main_argc_argv(_main_argc, _main_argv);
}

static void* _main_thread(void* param) {
// This is the main runtime thread for the application.
emscripten_set_thread_name(pthread_self(), "Application main thread");
__call_main(_main_argc, _main_argv);
// Will either call user's __main_void or weak version above.
int rtn = __main_void();
if (!emscripten_runtime_keepalive_check()) {
exit(rtn);
}
return NULL;
}

Expand Down
1 change: 0 additions & 1 deletion tests/other/metadce/test_metadce_minimal_pthreads.exports
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ A
B
C
D
E
o
p
q
Expand Down
1 change: 0 additions & 1 deletion tests/other/metadce/test_metadce_minimal_pthreads.funcs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ $emscripten_stack_set_limits
$get_tasks_for_thread
$init_file_lock
$init_mparams
$main
$memset
$pthread_mutexattr_destroy
$sbrk
Expand Down
2 changes: 1 addition & 1 deletion tests/other/metadce/test_metadce_minimal_pthreads.jssize
Original file line number Diff line number Diff line change
@@ -1 +1 @@
16131
16048
2 changes: 1 addition & 1 deletion tests/other/metadce/test_metadce_minimal_pthreads.size
Original file line number Diff line number Diff line change
@@ -1 +1 @@
18331
18318
4 changes: 4 additions & 0 deletions tests/pthread/test_pthread_proxying_cpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ void looper_main() {
}

void returner_main() {
// Return back to the event loop while keeping the runtime alive.
// Note that we can't use `emscripten_exit_with_live_runtime` here without
// introducing a memory leak due to way to C++11 threads interact with
// unwinding. See https://github.com/emscripten-core/emscripten/issues/17091.
emscripten_runtime_keepalive_push();
}

Expand Down
11 changes: 10 additions & 1 deletion tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -9185,6 +9185,15 @@ def test_pthread_dylink_longjmp(self):
main = test_file('core/pthread/test_pthread_dylink_longjmp.c')
self.dylink_testf(main, need_reverse=False)

@needs_dylink
@node_pthreads
def test_pthread_dylink_main_module_1(self):
self.emcc_args.append('-Wno-experimental')
self.set_setting('EXIT_RUNTIME')
self.set_setting('USE_PTHREADS')
self.set_setting('MAIN_MODULE')
self.do_runf(test_file('hello_world.c'))

@needs_dylink
@node_pthreads
def test_Module_dynamicLibraries_pthreads(self):
Expand Down Expand Up @@ -9249,7 +9258,7 @@ def test_undefined_main(self):
# In standalone we don't support implicitly building without main. The user has to explicitly
# opt out (see below).
err = self.expect_fail([EMCC, test_file('core/test_ctors_no_main.cpp')] + self.get_emcc_args())
self.assertContained('error: undefined symbol: main (referenced by top-level compiled C/C++ code)', err)
self.assertContained('error: undefined symbol: main/__main_argc_argv (referenced by top-level compiled C/C++ code)', err)
self.assertContained('warning: To build in STANDALONE_WASM mode without a main(), use emcc --no-entry', err)
elif not self.get_setting('LLD_REPORT_UNDEFINED') and not self.get_setting('STRICT'):
# Traditionally in emscripten we allow main to be implicitly undefined. This allows programs
Expand Down
2 changes: 1 addition & 1 deletion tests/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -11419,7 +11419,7 @@ def test_no_main_with_PROXY_TO_PTHREAD(self):
void foo() {}
''')
err = self.expect_fail([EMCC, 'lib.cpp', '-pthread', '-sPROXY_TO_PTHREAD'])
self.assertContained('emcc: error: PROXY_TO_PTHREAD proxies main() for you, but no main exists', err)
self.assertContained('error: PROXY_TO_PTHREAD proxies main() for you, but no main exists', err)

def test_archive_bad_extension(self):
# Regression test for https://github.com/emscripten-core/emscripten/issues/14012
Expand Down
5 changes: 3 additions & 2 deletions tools/system_libs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1728,8 +1728,9 @@ class libstubs(DebugLibrary):
# If main() is not in EXPORTED_FUNCTIONS, it may be dce'd out. This can be
# confusing, so issue a warning.
def warn_on_unexported_main(symbolses):
# In STANDALONE_WASM we don't expect main to be explictly exported
if settings.STANDALONE_WASM:
# In STANDALONE_WASM we don't expect main to be explictly exported.
# In PROXY_TO_PTHREAD we export emscripten_proxy_main instead of main.
if settings.STANDALONE_WASM or settings.PROXY_TO_PTHREAD:
return
if '_main' not in settings.EXPORTED_FUNCTIONS:
for symbols in symbolses:
Expand Down