diff --git a/ChangeLog.markdown b/ChangeLog.markdown index 837dbd7a2b271..80dee8af8178f 100644 --- a/ChangeLog.markdown +++ b/ChangeLog.markdown @@ -9,6 +9,7 @@ Not all changes are documented here. In particular, new features, user-oriented Current Trunk ------------- + - Breaking change: Similar to the getValue/setValue change from before (and with the same `ASSERTIONS` warnings to help users), do not export the following runtime methods by default: ccall, cwrap, allocate, Pointer_stringify, AsciiToString, stringToAscii, UTF8ArrayToString, UTF8ToString, stringToUTF8Array, stringToUTF8, lengthBytesUTF8, stackTrace, addOnPreRun, addOnInit, addOnPreMain, addOnExit, addOnPostRun, intArrayFromString, intArrayToString, writeStringToMemory, writeArrayToMemory, writeAsciiToMemory. v1.37.17: 12/4/2017 ------------------- diff --git a/emcc.py b/emcc.py index b5c348f65b415..5791e6b8bf983 100755 --- a/emcc.py +++ b/emcc.py @@ -946,6 +946,9 @@ def check(input_file): assert not (shared.Settings.NO_DYNAMIC_EXECUTION and options.use_closure_compiler), 'cannot have both NO_DYNAMIC_EXECUTION and closure compiler enabled at the same time' + if options.emrun: + shared.Settings.EXPORTED_RUNTIME_METHODS.append('addOnExit') + if options.use_closure_compiler: shared.Settings.USE_CLOSURE_COMPILER = options.use_closure_compiler if not shared.check_closure_compiler(): diff --git a/site/source/docs/api_reference/module.rst b/site/source/docs/api_reference/module.rst index d7081f66da089..066b2b0628cba 100644 --- a/site/source/docs/api_reference/module.rst +++ b/site/source/docs/api_reference/module.rst @@ -8,7 +8,7 @@ Module object Developers can provide an implementation of ``Module`` to control the execution of code. For example, to define how notification messages from Emscripten are displayed, developers implement the :js:attr:`Module.print` attribute. -.. note:: ``Module`` is also used to provide access to all Emscripten API functions (for example :js:func:`ccall`) in a way that avoids issues with function name minification at higher optimisation levels. These functions are documented as part of their own APIs. +.. note:: ``Module`` is also used to provide access to Emscripten API functions (for example :js:func:`ccall`) in a safe way. Any function or runtime method exported (using ``EXPORTED_FUNCTIONS`` for compiled functions, or ``EXTRA_EXPORTED_RUNTIME_METHODS`` for runtime methods like ``ccall``) will be accessible on the ``Module`` object, without minification changing the name, and the optimizer will make sure to keep the function present (and not remove it as unused). .. contents:: Table of Contents :local: diff --git a/site/source/docs/api_reference/preamble.js.rst b/site/source/docs/api_reference/preamble.js.rst index 1a44e41d434d5..6322905e1a7a9 100644 --- a/site/source/docs/api_reference/preamble.js.rst +++ b/site/source/docs/api_reference/preamble.js.rst @@ -6,9 +6,13 @@ preamble.js The JavaScript APIs in `preamble.js `_ provide programmatic access for interacting with the compiled C code, including: calling compiled C functions, accessing memory, converting pointers to JavaScript ``Strings`` and ``Strings`` to pointers (with different encodings/formats), and other convenience functions. -We call this "``preamble.js``" because Emscripten's output JS, at a high level, contains the preamble (from ``src/preamble.js``), then the compiled code, then the postamble. (In slightly more detail, the preamble contains utility functions and setup, while the postamble connects things and handles running the application.) Thus, the preamble code is included in the output JS, which means you can use the APIs described in this document without needing to do anything special. +We call this "``preamble.js``" because Emscripten's output JS, at a high level, contains the preamble (from ``src/preamble.js``), then the compiled code, then the postamble. (In slightly more detail, the preamble contains utility functions and setup, while the postamble connects things and handles running the application.) -.. note:: All functions should be called though the :ref:`Module ` object (for example: ``Module.functionName``). At optimisation ``-O2`` (and higher) function names are minified by the closure compiler, and calling them directly will fail. +The preamble code is included in the output JS, which is then optimized all together by the compiler, together with any ``--pre-js`` and ``--post-js`` files you added and code from any JavaScript libraries (``--js-library``). That means that you can call methods from the preamble directly, and the compiler will see that you need them, and not remove them as being unused. + +If you want to call preamble methods from somewhere the compiler can't see, like another script tag on the HTML, you need to **export** them. To do so, add them to ``EXTRA_EXPORTED_RUNTIME_METHODS`` (for example, ``-s 'EXTRA_EXPORTED_RUNTIME_METHODS=["ccall", "cwrap"]'`` will export ``call`` and ``cwrap``). Once exported, you can access them on the ``Module`` object (as ``Module.ccall``, for example). + +.. note:: If you try to use ``Module.ccall`` or another runtime method without exporting it, you will get an error. In a build with ``-s ASSERTIONS=1``, the compiler emits code to show you a useful error message, which will explain that you need to export it. In general, if you see something odd, it's useful to build with assertions. .. contents:: Table of Contents diff --git a/site/source/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.rst b/site/source/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.rst index 160ac96666b2f..560cdf47cf1c6 100644 --- a/site/source/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.rst +++ b/site/source/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.rst @@ -69,7 +69,9 @@ to prevent C++ name mangling. To compile this code run the following command in the Emscripten home directory:: - ./emcc tests/hello_function.cpp -o function.html -s EXPORTED_FUNCTIONS="['_int_sqrt']" + ./emcc tests/hello_function.cpp -o function.html -s EXPORTED_FUNCTIONS='["_int_sqrt"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' + +``EXPORTED_FUNCTIONS`` tells the compiler what we want to be accessible from the compiled code (everything else might be removed if it is not used), and ``EXTRA_EXPORTED_RUNTIME_METHODS`` tells the compiler that we want to use the runtime functions ``ccall`` and ``cwrap`` (otherwise, it will remove them if it does not see they are used). .. note:: @@ -146,9 +148,17 @@ parameters to pass to the function: as the latter will force the method to actually be included in the build. - - Use ``Module.ccall`` and not ``ccall`` by itself. The former will work - at all optimisation levels (even if the :term:`Closure Compiler` - minifies the function names). + - The compiler will remove code it does not see is used, to improve code + size. If you use ``ccall`` in a place it sees, like code in a ``--pre-js`` + or ``--post-js``, it will just work. If you use it in a place the compiler + didn't see, like another script tag on the HTML or in the JS console like + we did in this tutorial, then because of optimizations + and minification you should export ccall from the runtime, using + ``EXTRA_EXPORTED_RUNTIME_METHODS``, for example using + ``-s 'EXTRA_EXPORTED_RUNTIME_METHODS=["ccall", "cwrap"]'``, + and call it on ``Module`` (which contains + everything exported, in a safe way that is not influenced by minification + or optimizations). Interacting with an API written in C/C++ from NodeJS diff --git a/src/modules.js b/src/modules.js index dc922140c19e7..2cd2dee8b31d0 100644 --- a/src/modules.js +++ b/src/modules.js @@ -283,7 +283,7 @@ function maybeExport(name) { // check if it already exists, to support EXPORT_ALL and other cases // (we could optimize this, but in ASSERTIONS mode code size doesn't // matter anyhow) - return 'if (!Module["' + name + '"]) Module["' + name + '"] = function() { abort("\'' + name + '\' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS.") };'; + return 'if (!Module["' + name + '"]) Module["' + name + '"] = function() { abort("\'' + name + '\' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };'; } return ''; } diff --git a/src/preamble.js b/src/preamble.js index 34c2742b2f838..cafe847dc05b4 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -431,7 +431,7 @@ function Pointer_stringify(ptr, length) { } return ret; } - return Module['UTF8ToString'](ptr); + return UTF8ToString(ptr); } {{{ maybeExport('Pointer_stringify') }}} diff --git a/src/settings.js b/src/settings.js index faeba682c1576..87b0280d2fdc4 100644 --- a/src/settings.js +++ b/src/settings.js @@ -355,34 +355,12 @@ var EXPORTED_RUNTIME_METHODS = [ // Runtime elements that are exported on Module 'FS_createDevice', 'FS_unlink', 'Runtime', - 'ccall', - 'cwrap', 'ALLOC_NORMAL', 'ALLOC_STACK', 'ALLOC_STATIC', 'ALLOC_DYNAMIC', 'ALLOC_NONE', - 'allocate', 'getMemory', - 'Pointer_stringify', - 'AsciiToString', - 'stringToAscii', - 'UTF8ArrayToString', - 'UTF8ToString', - 'stringToUTF8Array', - 'stringToUTF8', - 'lengthBytesUTF8', - 'stackTrace', - 'addOnPreRun', - 'addOnInit', - 'addOnPreMain', - 'addOnExit', - 'addOnPostRun', - 'intArrayFromString', - 'intArrayToString', - 'writeStringToMemory', - 'writeArrayToMemory', - 'writeAsciiToMemory', 'addRunDependency', 'removeRunDependency', ]; diff --git a/tests/core/getValue_setValue_assert.txt b/tests/core/getValue_setValue_assert.txt index 6e3aff7a8ad3e..794dde0b174cd 100644 --- a/tests/core/getValue_setValue_assert.txt +++ b/tests/core/getValue_setValue_assert.txt @@ -1 +1 @@ -'setValue' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS. +'setValue' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ) diff --git a/tests/fs/test_lz4fs.cpp b/tests/fs/test_lz4fs.cpp index 7249030bb04e3..22270b4ca7894 100644 --- a/tests/fs/test_lz4fs.cpp +++ b/tests/fs/test_lz4fs.cpp @@ -147,7 +147,7 @@ int main() { console.log('seeing compressed size of ' + compressedSize + ', expect in ' + [low, high]); assert(compressedSize > low && compressedSize < high); // more than 1/3, because 1/3 is uncompressible, but still, less than 1/2 - Module['ccall']('finish'); + ccall('finish'); } var meta_xhr = new XMLHttpRequest(); diff --git a/tests/fs/test_workerfs_package.cpp b/tests/fs/test_workerfs_package.cpp index 9aeeffb7b697f..d5feab7d4aad9 100644 --- a/tests/fs/test_workerfs_package.cpp +++ b/tests/fs/test_workerfs_package.cpp @@ -56,7 +56,7 @@ int main() { packages: [{ metadata: meta, blob: blob }] }, '/files'); - Module.ccall('finish'); + ccall('finish'); } var meta_xhr = new XMLHttpRequest(); diff --git a/tests/test_browser.py b/tests/test_browser.py index 5f41f3b0d639c..2f9bd86648eb5 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -2155,7 +2155,7 @@ def test_runtime_misuse(self): self.btest(filename, expected='606', args=['--post-js', 'post.js', '--memory-init-file', '0', '-s', 'NO_EXIT_RUNTIME=1'] + extra_args + mode) def test_cwrap_early(self): - self.btest(os.path.join('browser', 'cwrap_early.cpp'), args=['-O2', '-s', 'ASSERTIONS=1', '--pre-js', path_from_root('tests', 'browser', 'cwrap_early.js')], expected='0') + self.btest(os.path.join('browser', 'cwrap_early.cpp'), args=['-O2', '-s', 'ASSERTIONS=1', '--pre-js', path_from_root('tests', 'browser', 'cwrap_early.js'), '-s', 'EXTRA_EXPORTED_RUNTIME_METHODS=["cwrap"]'], expected='0') def test_worker_api(self): Popen([PYTHON, EMCC, path_from_root('tests', 'worker_api_worker.cpp'), '-o', 'worker.js', '-s', 'BUILD_AS_WORKER=1', '-s', 'EXPORTED_FUNCTIONS=["_one"]']).communicate() @@ -3532,7 +3532,7 @@ def test_wasm_locate_file(self): self.run_browser('test.html', '', '/report_result?0') def test_utf8_textdecoder(self): - self.btest('benchmark_utf8.cpp', expected='0', args=['--embed-file', path_from_root('tests/utf8_corpus.txt') + '@/utf8_corpus.txt']) + self.btest('benchmark_utf8.cpp', expected='0', args=['--embed-file', path_from_root('tests/utf8_corpus.txt') + '@/utf8_corpus.txt', '-s', 'EXTRA_EXPORTED_RUNTIME_METHODS=["UTF8ToString"]']) def test_utf16_textdecoder(self): self.btest('benchmark_utf16.cpp', expected='0', args=['--embed-file', path_from_root('tests/utf16_corpus.txt') + '@/utf16_corpus.txt', '-s', 'EXTRA_EXPORTED_RUNTIME_METHODS=["UTF16ToString","stringToUTF16","lengthBytesUTF16"]']) diff --git a/tests/test_core.py b/tests/test_core.py index 261e78bff68ef..fa225edc4cdf3 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -4412,7 +4412,7 @@ def test_utime(self): def test_utf(self): self.banned_js_engines = [SPIDERMONKEY_ENGINE] # only node handles utf well Settings.EXPORTED_FUNCTIONS = ['_main', '_malloc'] - Settings.EXTRA_EXPORTED_RUNTIME_METHODS = ['getValue', 'setValue'] + Settings.EXTRA_EXPORTED_RUNTIME_METHODS = ['getValue', 'setValue', 'UTF8ToString', 'stringToUTF8'] self.do_run_in_out_file_test('tests', 'core', 'test_utf') def test_utf32(self): @@ -4421,10 +4421,12 @@ def test_utf32(self): self.do_run(open(path_from_root('tests', 'utf32.cpp')).read(), 'OK.', args=['-fshort-wchar']) def test_utf8(self): + Settings.EXTRA_EXPORTED_RUNTIME_METHODS = ['UTF8ToString', 'stringToUTF8', 'AsciiToString', 'stringToAscii'] Building.COMPILER_TEST_OPTS += ['-std=c++11'] self.do_run(open(path_from_root('tests', 'utf8.cpp')).read(), 'OK.') def test_utf8_textdecoder(self): + Settings.EXTRA_EXPORTED_RUNTIME_METHODS = ['UTF8ToString', 'stringToUTF8'] Building.COMPILER_TEST_OPTS += ['--embed-file', path_from_root('tests/utf8_corpus.txt')+ '@/utf8_corpus.txt'] self.do_run(open(path_from_root('tests', 'benchmark_utf8.cpp')).read(), 'OK.') @@ -5905,6 +5907,7 @@ def test_autodebug(self): @sync def test_ccall(self): + Settings.EXTRA_EXPORTED_RUNTIME_METHODS = ['ccall', 'cwrap'] post = ''' def process(filename): src = open(filename, 'r').read() + \'\'\' @@ -6854,10 +6857,10 @@ def test_exit_status(self): } ''' open('post.js', 'w').write(''' - Module.addOnExit(function () { + addOnExit(function () { Module.print('I see exit status: ' + EXITSTATUS); }); - Module.callMain(); + Module['callMain'](); ''') self.emcc_args += ['-s', 'INVOKE_RUN=0', '--post-js', 'post.js'] self.do_run(src.replace('CAPITAL_EXIT', '0'), 'hello, world!\ncleanup\nI see exit status: 118') @@ -6934,7 +6937,7 @@ def test_async(self): Settings.INVOKE_RUN = 0 open('post.js', 'w').write(''' try { - Module['ccall']('main', 'number', ['number', 'string'], [2, 'waka']); + ccall('main', 'number', ['number', 'string'], [2, 'waka']); var never = true; } catch(e) { Module.print(e); @@ -6955,7 +6958,7 @@ def test_async(self): } ''' open('post.js', 'w').write(''' -Module['ccall']('main', null, ['number', 'string'], [2, 'waka'], { async: true }); +ccall('main', null, ['number', 'string'], [2, 'waka'], { async: true }); ''') self.do_run(src, 'HelloWorld'); diff --git a/tests/test_other.py b/tests/test_other.py index e4135f11c82b1..c9681275ede17 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -2465,7 +2465,7 @@ def test_module_exports_with_closure(self): self.clear() # compile with -O2 --closure 0 - Popen([PYTHON, EMCC, path_from_root('tests', 'Module-exports', 'test.c'), '-o', 'test.js', '-O2', '--closure', '0', '--pre-js', path_from_root('tests', 'Module-exports', 'setup.js'), '-s', 'EXPORTED_FUNCTIONS=["_bufferTest"]'], stdout=PIPE, stderr=PIPE).communicate() + Popen([PYTHON, EMCC, path_from_root('tests', 'Module-exports', 'test.c'), '-o', 'test.js', '-O2', '--closure', '0', '--pre-js', path_from_root('tests', 'Module-exports', 'setup.js'), '-s', 'EXPORTED_FUNCTIONS=["_bufferTest"]', '-s', 'EXTRA_EXPORTED_RUNTIME_METHODS=["ccall", "cwrap"]'], stdout=PIPE, stderr=PIPE).communicate() # Check that compilation was successful assert os.path.exists('test.js') @@ -2820,6 +2820,7 @@ def test_proxyfs(self): Popen([PYTHON, EMCC, '-o', 'proxyfs_test.js', 'proxyfs_test.c', '--embed-file', 'proxyfs_embed.txt', '--pre-js', 'proxyfs_pre.js', + '-s', 'EXTRA_EXPORTED_RUNTIME_METHODS=["ccall", "cwrap"]', '-s', 'MAIN_MODULE=1']).communicate() # Following shutil.copyfile just prevent 'require' of node.js from caching js-object. # See https://nodejs.org/api/modules.html @@ -4874,7 +4875,7 @@ def do(name, source, moar_opts): assert sizes['no_nuthin'] < ratio*sizes['normal'] assert sizes['no_nuthin'] < absolute, str(sizes['no_nuthin']) + ' >= ' + str(absolute) if '--closure' in opts: # no EXPORTED_RUNTIME_METHODS makes closure much more effective - assert sizes['no_nuthin'] < 0.975*sizes['no_fs'] + assert sizes['no_nuthin'] < 0.995*sizes['no_fs'] assert sizes['no_fs_manual'] < sizes['no_fs'] # manual can remove a tiny bit more test(['-s', 'ASSERTIONS=0'], 0.75, 360000) # we don't care about code size with assertions test(['-O1'], 0.66, 210000) @@ -4900,13 +4901,11 @@ def do(name, moar_opts): assert sizes['no_nuthin'] < sizes['normal'] assert sizes['no_nuthin'] < ratio*sizes['normal'] assert sizes['no_nuthin'] < absolute - if '--closure' in opts: # no EXPORTED_RUNTIME_METHODS makes closure much more effective - assert sizes['no_nuthin'] < 0.975*sizes['normal'] test(['-s', 'ASSERTIONS=0'], 1, 220000) # we don't care about code size with assertions test(['-O1'], 1, 215000) - test(['-O2'], 0.99, 75000) - test(['-O3', '--closure', '1'], 0.975, 50000) - test(['-O3', '--closure', '2'], 0.975, 41000) # might change now and then + test(['-O2'], 0.995, 55000) + test(['-O3', '--closure', '1'], 0.995, 38000) + test(['-O3', '--closure', '2'], 0.995, 35000) # might change now and then def test_no_browser(self): BROWSER_INIT = 'var Browser' @@ -4929,10 +4928,10 @@ def test(opts, has, not_has): self.assertContained(has, src) self.assertNotContained(not_has, src) - test([], 'Module["intArray', 'Module["waka') - test(['-s', 'EXPORTED_RUNTIME_METHODS=[]'], '', 'Module["intArray') - test(['-s', 'EXPORTED_RUNTIME_METHODS=["intArrayToString"]'], 'Module["intArray', 'Module["waka') - test(['-s', 'EXPORTED_RUNTIME_METHODS=[]', '-s', 'EXTRA_EXPORTED_RUNTIME_METHODS=["intArrayToString"]'], 'Module["intArray', 'Module["waka') + test([], 'Module["getMemory', 'Module["waka') + test(['-s', 'EXPORTED_RUNTIME_METHODS=[]'], '', 'Module["getMemory') + test(['-s', 'EXPORTED_RUNTIME_METHODS=["getMemory"]'], 'Module["getMemory', 'Module["waka') + test(['-s', 'EXPORTED_RUNTIME_METHODS=[]', '-s', 'EXTRA_EXPORTED_RUNTIME_METHODS=["getMemory"]'], 'Module["getMemory', 'Module["waka') def test_stat_fail_alongtheway(self): open('src.cpp', 'w').write(r''' diff --git a/tools/ports/sdl.py b/tools/ports/sdl.py index a52879f4689a8..492f9d0cb2b65 100644 --- a/tools/ports/sdl.py +++ b/tools/ports/sdl.py @@ -56,6 +56,7 @@ def process_args(ports, args, settings, shared): elif settings.USE_SDL == 2: get(ports, settings, shared) args += ['-Xclang', '-isystem' + os.path.join(shared.Cache.get_path('ports-builds'), 'sdl2', 'include')] + settings.EXPORTED_RUNTIME_METHODS.append('Pointer_stringify') return args def show():