Skip to content

Commit f585dcb

Browse files
authored
NO_EXIT_RUNTIME by default (#5878)
This makes us not exit the runtime by default. That means we don't emit code for atexits and other things that happen when the runtime shuts down, like flushing the stdio streams. This is beneficial for 2 reasons: * For #5794, this helps remove code. It avoids all the support for shutting down the runtime, emitting atexits, etc. It also enables more optimizations (the ctor evaller in wasm can do better without calls to atexit). This removes 3% of hello world's wasm size and 0.5% of its JS. * A saner default for the web. A program on the web that does anything asynchronous will not want the runtime to exit when main() exits, so we set this flag to 1 for many tests, which this PR lets us remove. However, this is a breaking change. As already mentioned, the possible breakages are * printf("hello") will not console.log since there is no newline. Only when the streams are flushed would that be printed out. So this change would make us not emit that. * atexits do not run. Both of those risks are mitigated in this PR: In ASSERTIONS mode, check if there is unflushed stream output, and explain what to do if so. Same if atexit is called. This PR has a lot of test changes, some that simplify web tests - because the new default is better for the web - but others that add a param to a shell test - because the new default is less optimal in a shell environment. I think the risk here is lower than those shell tests indicate: we do test quite a lot of things in the shell, but just because it's convenient, not because that's what most users care about. Also: * this PR found an unnoticed bug: FORCE_FILESYSTEM didn't actually do what the name suggests. I think we just never tested it properly with NO_EXIT_RUNTIME. Fixed in this PR. * emrun sets NO_EXIT_RUNTIME=0. it is a mode where we specifically want to get the exit code from the running program, as if it were a shell command, not a browser app * add faq entry, and mention the faq * fix an existing emterpreter-async bug: if we are unwinding the stack as we leave main(), then do not call exit, we are not exiting yet - code is yet to run later * metadce is now more effective, update test * faq entry on Module.* is not a function * fix browser.test_emscripten_main_loop - the pthreads part needs the runtime to exit
1 parent c057242 commit f585dcb

18 files changed

+322
-123
lines changed

ChangeLog.markdown

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Not all changes are documented here. In particular, new features, user-oriented
99

1010
Current Trunk
1111
-------------
12+
- Breaking change: Change `NO_EXIT_RUNTIME` to 1 by default. This means that by default we don't include code to shut down the runtime, flush stdio streams, run atexits, etc., which is better for code size. When `ASSERTIONS` is on, we warn at runtime if there is text buffered in the streams that should be flushed, or atexits are used.
1213
- Meta-DCE for JS+wasm: remove unused code between JS+wasm more aggressively. This should not break valid code, but may break code that depended on unused code being kept around (like using a function from outside the emitted JS without exporting it - only exported things are guaranteed to be kept alive through optimization).
1314

1415
v1.37.24: 12/13/2017

emcc.py

+11-8
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,9 @@ def filter_emscripten_options(argv):
479479
# The preprocessor define EMSCRIPTEN is deprecated. Don't pass it to code in strict mode. Code should use the define __EMSCRIPTEN__ instead.
480480
if not shared.Settings.STRICT:
481481
cmd += ['-DEMSCRIPTEN']
482-
if use_js: cmd += ['-s', 'ERROR_ON_UNDEFINED_SYMBOLS=1'] # configure tests should fail when an undefined symbol exists
482+
if use_js:
483+
cmd += ['-s', 'ERROR_ON_UNDEFINED_SYMBOLS=1'] # configure tests should fail when an undefined symbol exists
484+
cmd += ['-s', 'NO_EXIT_RUNTIME=0'] # configure tests want a more shell-like style, where we emit return codes on exit()
483485

484486
logging.debug('just configuring: ' + ' '.join(cmd))
485487
if debug_configure: open(tempout, 'a').write('emcc, just configuring: ' + ' '.join(cmd) + '\n\n')
@@ -663,6 +665,8 @@ def detect_fixed_language_mode(args):
663665
if options.emrun:
664666
options.pre_js += open(shared.path_from_root('src', 'emrun_prejs.js')).read() + '\n'
665667
options.post_js += open(shared.path_from_root('src', 'emrun_postjs.js')).read() + '\n'
668+
# emrun mode waits on program exit
669+
shared.Settings.NO_EXIT_RUNTIME = 0
666670

667671
if options.cpu_profiler:
668672
options.post_js += open(shared.path_from_root('src', 'cpuprofiler.js')).read() + '\n'
@@ -1072,9 +1076,12 @@ def check(input_file):
10721076

10731077
if not shared.Settings.NO_FILESYSTEM and not shared.Settings.ONLY_MY_CODE:
10741078
shared.Settings.EXPORTED_FUNCTIONS += ['___errno_location'] # so FS can report errno back to C
1075-
if not shared.Settings.NO_EXIT_RUNTIME:
1076-
shared.Settings.EXPORTED_FUNCTIONS += ['_fflush'] # to flush the streams on FS quit
1077-
# TODO this forces 4 syscalls, maybe we should avoid it?
1079+
# to flush streams on FS exit, we need to be able to call fflush
1080+
# we only include it if the runtime is exitable, or when ASSERTIONS
1081+
# (ASSERTIONS will check that streams do not need to be flushed,
1082+
# helping people see when they should have disabled NO_EXIT_RUNTIME)
1083+
if not shared.Settings.NO_EXIT_RUNTIME or shared.Settings.ASSERTIONS:
1084+
shared.Settings.EXPORTED_FUNCTIONS += ['_fflush']
10781085

10791086
if shared.Settings.USE_PTHREADS:
10801087
if not any(s.startswith('PTHREAD_POOL_SIZE=') for s in settings_changes):
@@ -1209,10 +1216,6 @@ def check(input_file):
12091216
if 'interpret' in shared.Settings.BINARYEN_METHOD:
12101217
logging.warning('disabling EVAL_CTORS as the bundled interpreter confuses the ctor tool')
12111218
shared.Settings.EVAL_CTORS = 0
1212-
else:
1213-
# for wasm, we really want no-exit-runtime, so that atexits don't stop us
1214-
if final_suffix in JS_CONTAINING_SUFFIXES and not shared.Settings.NO_EXIT_RUNTIME:
1215-
logging.warning('you should enable -s NO_EXIT_RUNTIME=1 so that EVAL_CTORS can work at full efficiency (it gets rid of atexit calls which might disrupt EVAL_CTORS)')
12161219

12171220
# memory growth does not work in dynamic linking, except for wasm
12181221
if not shared.Settings.WASM and (shared.Settings.MAIN_MODULE or shared.Settings.SIDE_MODULE):

site/source/docs/api_reference/emscripten.h.rst

+2
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,8 @@ Functions
334334
335335
The difference is that ``emscripten_force_exit`` will shut down the runtime even if you previously called :c:func:`emscripten_exit_with_live_runtime` or otherwise kept the runtime alive. In other words, this method gives you the option to completely shut down the runtime after it was kept alive beyond the completion of ``main()``.
336336
337+
Note that if ``NO_EXIT_RUNTIME`` is set (which it is by default) then the runtime cannot be shut down, as we do not include the code to do so. Build with ``-s NO_EXIT_RUNTIME=0`` if you want to be able to exit the runtime.
338+
337339
:param int status: The same as for the *libc* function `exit() <http://linux.die.net/man/3/exit>`_.
338340
339341
.. c:function:: double emscripten_get_device_pixel_ratio(void)

site/source/docs/getting_started/FAQ.rst

+30-1
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ Another option is to define an ``onRuntimeInitialized`` function,
230230

231231
Module['onRuntimeInitialized'] = function() { ... };
232232

233-
That method will be called when the runtime is ready and it is ok for you to call compiled code. In practice, that is exactly the same time at which ``main()`` would be called, so ``onRuntimeInitialized`` doesn't let you do anything new, but it can be convenient in some cases - for example, if you use ``onRuntimeInitialized`` and don't define a ``main()`` function, then the runtime will not be shut down after ``main()`` exits, and you can keep calling compiled methods (you can also have a ``main()`` and build with ``-s NO_EXIT_RUNTIME=1`` to keep the runtime from being shut down). Thus, for libraries, ``onRuntimeInitialized`` can be convenient.
233+
That method will be called when the runtime is ready and it is ok for you to call compiled code. In practice, that is exactly the same time at which ``main()`` would be called, so ``onRuntimeInitialized`` doesn't let you do anything new, but you can set it from JavaScript at runtime in a flexible way.
234234

235235
Here is an example of how to use it:
236236

@@ -248,6 +248,27 @@ Here is an example of how to use it:
248248
The crucial thing is that ``Module`` exists, and has the property ``onRuntimeInitialized``, before the script containing emscripten output (``my_project.js`` in this example) is loaded.
249249

250250

251+
.. _faq-NO_EXIT_RUNTIME:
252+
253+
What does "exiting the runtime" mean? Why don't ``atexit()s`` run?
254+
==================================================================
255+
256+
(You may need this answer if you see an error saying something like ``atexit() called, but NO_EXIT_RUNTIME`` or ``stdio streams had content in them that was not flushed. you should set NO_EXIT_RUNTIME to 0``.)
257+
258+
By default Emscripten sets ``NO_EXIT_RUNTIME=1``, which means that we don't include code to shut down the runtime. That means that when ``main()`` exits, we don't flush the stdio streams, or call the destructors of global C++ objects, or call ``atexit`` callbacks. This lets us emit smaller code by default, and is normally what you want on the web: even though ``main()`` exited, you may have something asynchronous happening later that you want to execute.
259+
260+
In some cases, though, you may want a more "commandline" experience, where we do shut down the runtime when ``main()`` exits. You can build with ``-s NO_EXIT_RUNTIME=0``, and then we will call ``atexits`` and so forth. When you build with ``ASSERTIONS``, you should get a warning when you need this. For example, if your program prints something without a newline,
261+
262+
::
263+
264+
#include <stdio.h>
265+
266+
int main() {
267+
printf("hello"); // note no newline
268+
}
269+
270+
If we don't shut down the runtime and flush the stdio streams, "hello" won't be printed. In an ``ASSERTIONS`` build you'll get a notification saying ``stdio streams had content in them that was not flushed. you should set NO_EXIT_RUNTIME to 0``.
271+
251272
.. _faq-dead-code-elimination:
252273

253274
Why do functions in my C/C++ source code vanish when I compile to JavaScript, and/or I get ``No functions to process``?
@@ -324,6 +345,14 @@ Emscripten by default does *not* give fatal errors on undefined symbols, so you
324345

325346
Aside from just forgetting to link in a necessary object file, one possible cause for this error is inline functions in headers. If you have a header with ``inline int my_func() { .. }`` then *Clang* may not actually inline the function (since inline is just a hint), and also not generate code for it (since it's in a header). The result is that the generated bitcode and JavaScript will not have that function implemented. One solution is to add ``static`` to the function declaration, which forces code to be generated in the object file: ``static inline int my_func() { .. }``.
326347

348+
Why do I get ``TypeError: Module.someThing is not a function``?
349+
===============================================================
350+
351+
The ``Module`` object will contain exported methods. For something to appear there, you should add it to ``EXPORTED_FUNCTIONS`` for compiled code, or ``EXTRA_EXPORTED_RUNTIME_METHODS`` for a runtime method (like ``getValue``).
352+
353+
.. note:: You can use runtime methods directly, without exporting them, if the compiler can see them used. For example, you can use ``getValue`` in ``EM_ASM`` code, or a ``--pre-js``, by calling it directly. The optimizer will not remove that JS runtime method because it sees it is used. You only need to use ``Module.getValue`` if you want to call that method from outside the JS code the compiler can see, and then you need to export it.
354+
355+
.. note:: Emscripten used to export many runtime methods by default. This increased code size, and for that reason we've changed that default. If you depend on something that used to be exported, you should see a warning pointing you to the solution, in an unoptimized build, or a build with ``ASSERTIONS`` enabled, which we hope will minimize any annoyance. See ``Changelog.markdown`` for details.
327356

328357
Why do I get an odd python error complaining about libcxx.bc or libcxxabi.bc?
329358
=============================================================================

site/source/docs/optimizing/Optimizing-Code.rst

-12
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,6 @@ Advanced compiler settings
3838

3939
There are several flags you can :ref:`pass to the compiler <emcc-s-option-value>` to affect code generation, which will also affect performance — for example :ref:`DISABLE_EXCEPTION_CATCHING <optimizing-code-exception-catching>`. These are documented in `src/settings.js <https://github.com/kripken/emscripten/blob/master/src/settings.js>`_. Some of these will be directly affected by the optimization settings (you can find out which ones by searching for ``apply_opt_level`` in `tools/shared.py <https://github.com/kripken/emscripten/blob/1.29.12/tools/shared.py#L958>`_).
4040

41-
A few useful flags are:
42-
43-
-
44-
.. _optimizing-code-no-exit-runtime:
45-
46-
``NO_EXIT_RUNTIME``: Building with ``-s NO_EXIT_RUNTIME=1`` lets the compiler know that you don't want to shut down the runtime environment after the ``main()`` function finishes. This allows it to discard the ``atexit`` and global destructor calls it would otherwise make, improving code size and startup speed.
47-
48-
This is useful if your ``main()`` function finishes but you still want to execute code, for example in an app that uses a :ref:`main loop function <emscripten-runtime-environment-main-loop>`.
49-
50-
.. note:: Emscripten will not shut down the runtime if it detects :c:func:`emscripten_set_main_loop`, but it is better to optimise away the unnecessary code.
51-
52-
5341

5442
Code size
5543
=========

site/source/docs/tools_reference/emcc.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ Options that are modified or new in *emcc* are listed below:
405405
.. _emcc-emrun:
406406

407407
``--emrun``
408-
Enables the generated output to be aware of the :ref:`emrun <Running-html-files-with-emrun>` command line tool. This allows ``stdout``, ``stderr`` and ``exit(returncode)`` capture when running the generated application through *emrun*.
408+
Enables the generated output to be aware of the :ref:`emrun <Running-html-files-with-emrun>` command line tool. This allows ``stdout``, ``stderr`` and ``exit(returncode)`` capture when running the generated application through *emrun*. (This enables `NO_EXIT_RUNTIME=0`, allowing normal runtime exiting with return code passing.)
409409

410410
``--cpuprofiler``
411411
Embeds a simple CPU profiler onto the generated page. Use this to perform cursory interactive performance profiling.

src/library.js

+5
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,11 @@ LibraryManager.library = {
596596
atexit__proxy: 'sync',
597597
atexit__sig: 'ii',
598598
atexit: function(func, arg) {
599+
#if ASSERTIONS
600+
#if NO_EXIT_RUNTIME == 1
601+
Runtime.warnOnce('atexit() called, but NO_EXIT_RUNTIME is set, so atexits() will not be called. set NO_EXIT_RUNTIME to 0 (see the FAQ)');
602+
#endif
603+
#endif
599604
__ATEXIT__.unshift({ func: func, arg: arg });
600605
},
601606
__cxa_atexit: 'atexit',

src/library_browser.js

+5
Original file line numberDiff line numberDiff line change
@@ -1270,6 +1270,11 @@ var LibraryBrowser = {
12701270
emscripten_force_exit__proxy: 'sync',
12711271
emscripten_force_exit__sig: 'vi',
12721272
emscripten_force_exit: function(status) {
1273+
#if NO_EXIT_RUNTIME
1274+
#if ASSERTIONS
1275+
Runtime.warnOnce('emscripten_force_exit cannot actually shut down the runtime, as the build has NO_EXIT_RUNTIME set');
1276+
#endif
1277+
#endif
12731278
Module['noExitRuntime'] = false;
12741279
Module['exit'](status);
12751280
},

src/library_fs.js

+4
Original file line numberDiff line numberDiff line change
@@ -1921,3 +1921,7 @@ mergeInto(LibraryManager.library, {
19211921
}
19221922
});
19231923

1924+
if (FORCE_FILESYSTEM) {
1925+
DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.push('$FS');
1926+
}
1927+

src/library_syscall.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,20 @@ var SyscallsLibrary = {
779779
return SYSCALLS.doReadv(stream, iov, iovcnt);
780780
},
781781
#if NO_FILESYSTEM
782-
__syscall146__postset: '/* flush anything remaining in the buffer during shutdown */ __ATEXIT__.push(function() { var fflush = Module["_fflush"]; if (fflush) fflush(0); var printChar = ___syscall146.printChar; if (!printChar) return; var buffers = ___syscall146.buffers; if (buffers[1].length) printChar(1, {{{ charCode("\n") }}}); if (buffers[2].length) printChar(2, {{{ charCode("\n") }}}); });',
782+
$flush_NO_FILESYSTEM: function() {
783+
// flush anything remaining in the buffers during shutdown
784+
var fflush = Module["_fflush"];
785+
if (fflush) fflush(0);
786+
var printChar = ___syscall146.printChar;
787+
if (!printChar) return;
788+
var buffers = ___syscall146.buffers;
789+
if (buffers[1].length) printChar(1, {{{ charCode("\n") }}});
790+
if (buffers[2].length) printChar(2, {{{ charCode("\n") }}});
791+
},
792+
__syscall146__deps: ['$flush_NO_FILESYSTEM'],
793+
#if NO_EXIT_RUNTIME == 0
794+
__syscall146__postset: '__ATEXIT__.push(flush_NO_FILESYSTEM);',
795+
#endif
783796
#endif
784797
__syscall146: function(which, varargs) { // writev
785798
#if NO_FILESYSTEM == 0

src/postamble.js

+58-5
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,16 @@ Module['callMain'] = function callMain(args) {
212212
Module.realPrint('main() took ' + (Date.now() - start) + ' milliseconds');
213213
#endif
214214

215+
#if EMTERPRETIFY_ASYNC
216+
// if we are saving the stack, then do not call exit, we are not
217+
// really exiting now, just unwinding the JS stack
218+
if (EmterpreterAsync.state !== 1) {
219+
#endif // EMTERPRETIFY_ASYNC
215220
// if we're not running an evented main loop, it's time to exit
216-
exit(ret, /* implicit = */ true);
221+
exit(ret, /* implicit = */ true);
222+
#if EMTERPRETIFY_ASYNC
223+
}
224+
#endif // EMTERPRETIFY_ASYNC
217225
}
218226
catch(e) {
219227
if (e instanceof ExitStatus) {
@@ -313,17 +321,62 @@ function run(args) {
313321
Module['run'] = run;
314322

315323
function exit(status, implicit) {
316-
if (implicit && Module['noExitRuntime']) {
317324
#if ASSERTIONS
318-
Module.printErr('exit(' + status + ') implicitly called by end of main(), but noExitRuntime, so not exiting the runtime (you can use emscripten_force_exit, if you want to force a true shutdown)');
325+
#if NO_EXIT_RUNTIME == 1
326+
// Compiler settings do not allow exiting the runtime, so flushing
327+
// the streams is not possible. but in ASSERTIONS mode we check
328+
// if there was something to flush, and if so tell the user they
329+
// should request that the runtime be exitable.
330+
// Normally we would not even include flush() at all, but in ASSERTIONS
331+
// builds we do so just for this check, and here we see if there is any
332+
// content to flush, that is, we check if there would have been
333+
// something a non-ASSERTIONS build would have not seen.
334+
// How we flush the streams depends on whether we are in NO_FILESYSTEM
335+
// mode (which has its own special function for this; otherwise, all
336+
// the code is inside libc)
337+
#if NO_FILESYSTEM
338+
var flush = {{{ '$flush_NO_FILESYSTEM' in addedLibraryItems ? 'flush_NO_FILESYSTEM' : 'null' }}};
339+
#else
340+
var flush = {{{ '$FS' in addedLibraryItems ? 'FS.quit' : "Module['_fflush']" }}};
319341
#endif
342+
if (flush) {
343+
var print = Module['print'];
344+
var printErr = Module['printErr'];
345+
var has = false;
346+
Module['print'] = Module['printErr'] = function(x) {
347+
has = true;
348+
}
349+
try { // it doesn't matter if it fails
350+
flush(0);
351+
} catch(e) {}
352+
Module['print'] = print;
353+
Module['printErr'] = printErr;
354+
if (has) {
355+
Runtime.warnOnce('stdio streams had content in them that was not flushed. you should set NO_EXIT_RUNTIME to 0 (see the FAQ), or make sure to emit a newline when you printf etc.');
356+
}
357+
}
358+
#endif // NO_EXIT_RUNTIME
359+
#endif // ASSERTIONS
360+
361+
// if this is just main exit-ing implicitly, and the status is 0, then we
362+
// don't need to do anything here and can just leave. if the status is
363+
// non-zero, though, then we need to report it.
364+
// (we may have warned about this earlier, if a situation justifies doing so)
365+
if (implicit && Module['noExitRuntime'] && status === 0) {
320366
return;
321367
}
322368

323369
if (Module['noExitRuntime']) {
324370
#if ASSERTIONS
325-
Module.printErr('exit(' + status + ') called, but noExitRuntime, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)');
326-
#endif
371+
// if exit() was called, we may warn the user if the runtime isn't actually being shut down
372+
if (!implicit) {
373+
#if NO_EXIT_RUNTIME
374+
Module.printErr('exit(' + status + ') called, but NO_EXIT_RUNTIME is set, so halting execution but not exiting the runtime or preventing further async execution (build with NO_EXIT_RUNTIME=0, if you want a true shutdown)');
375+
#else
376+
Module.printErr('exit(' + status + ') called, but noExitRuntime is set due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)');
377+
#endif // NO_EXIT_RUNTIME
378+
}
379+
#endif // ASSERTIONS
327380
} else {
328381
#if USE_PTHREADS
329382
PThread.terminateAllThreads();

src/pthread-main.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,7 @@ this.onmessage = function(e) {
156156
}
157157
// The thread might have finished without calling pthread_exit(). If so, then perform the exit operation ourselves.
158158
// (This is a no-op if explicit pthread_exit() had been called prior.)
159-
if (!Module['noExitRuntime']) PThread.threadExit(result);
160-
else console.log('pthread noExitRuntime: not quitting.');
159+
PThread.threadExit(result);
161160
} else if (e.data.cmd === 'cancel') { // Main thread is asking for a pthread_cancel() on this thread.
162161
if (threadInfoStruct && PThread.thisThreadCancelState == 0/*PTHREAD_CANCEL_ENABLE*/) {
163162
PThread.threadCancel();

src/settings.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,12 @@ var VERBOSE = 0; // When set to 1, will generate more verbose output during comp
4444
var INVOKE_RUN = 1; // Whether we will run the main() function. Disable if you embed the generated
4545
// code in your own, and will call main() yourself at the right time (which you
4646
// can do with Module.callMain(), with an optional parameter of commandline args).
47-
var NO_EXIT_RUNTIME = 0; // If set, the runtime is not quit when main() completes (allowing code to
48-
// run afterwards, for example from the browser main event loop).
47+
var NO_EXIT_RUNTIME = 1; // If 1, the runtime is not quit when main() completes (allowing code to
48+
// run afterwards, for example from the browser main event loop). atexit()s
49+
// are also not executed, and we can avoid including code for runtime shutdown,
50+
// like flushing the stdio streams.
51+
// Set this to 0 if you do want atexit()s or stdio streams to be flushed
52+
// on exit.
4953
var MEM_INIT_METHOD = 0; // How to represent the initial memory content.
5054
// 0: embed a base64 string literal representing the initial memory data
5155
// 1: create a *.mem file containing the binary data of the initial memory;

0 commit comments

Comments
 (0)