Skip to content

Less default runtime exports #5892

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 24 commits into from
Dec 6, 2017
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
1 change: 1 addition & 0 deletions ChangeLog.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------------
Expand Down
3 changes: 3 additions & 0 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
2 changes: 1 addition & 1 deletion site/source/docs/api_reference/module.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 6 additions & 2 deletions site/source/docs/api_reference/preamble.js.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ preamble.js

The JavaScript APIs in `preamble.js <https://github.com/kripken/emscripten/blob/master/src/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 <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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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::

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 '';
}
Expand Down
2 changes: 1 addition & 1 deletion src/preamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ function Pointer_stringify(ptr, length) {
}
return ret;
}
return Module['UTF8ToString'](ptr);
return UTF8ToString(ptr);
}
{{{ maybeExport('Pointer_stringify') }}}

Expand Down
22 changes: 0 additions & 22 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
];
Expand Down
2 changes: 1 addition & 1 deletion tests/core/getValue_setValue_assert.txt
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion tests/fs/test_lz4fs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion tests/fs/test_workerfs_package.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ int main() {
packages: [{ metadata: meta, blob: blob }]
}, '/files');

Module.ccall('finish');
ccall('finish');
}

var meta_xhr = new XMLHttpRequest();
Expand Down
4 changes: 2 additions & 2 deletions tests/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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"]'])
Expand Down
13 changes: 8 additions & 5 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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.')

Expand Down Expand Up @@ -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() + \'\'\'
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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);
Expand All @@ -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');

Expand Down
21 changes: 10 additions & 11 deletions tests/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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'
Expand All @@ -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'''
Expand Down
1 change: 1 addition & 0 deletions tools/ports/sdl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down