Skip to content

Commit 3bc1f9f

Browse files
authored
Less default runtime exports (#5892)
More for #5836 (JS shrinking). This removes almost all default function exports from the runtime. In ASSERTIONS builds, a warning will be shown if they are used, using the mechanism we introduced for getValue/setValue (which we recently removed from being exported by default). This reduces the size of the #5836 testcase by almost 20% (!), shrinking us from 21.75 k to 17.65 k. There are still a few things exported by default, like filesystem support (the file packager emits code that uses those, we should make extra sure this is not confusing for people) and things that aren't functions (need to investigate a good mechanism for warning if they are used incorrectly in ASSERTIONS mode, perhaps a getter on the Module object). This PR also includes * Changelog update explaining the change. * Docs improvements. * An example in the SDL port of how to export runtime stuff if the port needs it (so e.g. people using SDL don't need to manually add exports, the port can do it for them).
1 parent 89f9fe5 commit 3bc1f9f

File tree

15 files changed

+51
-52
lines changed

15 files changed

+51
-52
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: 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.
1213

1314
v1.37.17: 12/4/2017
1415
-------------------

emcc.py

+3
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,9 @@ def check(input_file):
937937

938938
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'
939939

940+
if options.emrun:
941+
shared.Settings.EXPORTED_RUNTIME_METHODS.append('addOnExit')
942+
940943
if options.use_closure_compiler:
941944
shared.Settings.USE_CLOSURE_COMPILER = options.use_closure_compiler
942945
if not shared.check_closure_compiler():

site/source/docs/api_reference/module.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Module object
88

99
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.
1010

11-
.. 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.
11+
.. 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).
1212

1313
.. contents:: Table of Contents
1414
:local:

site/source/docs/api_reference/preamble.js.rst

+6-2
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ preamble.js
66

77
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.
88

9-
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.
9+
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.)
1010

11-
.. 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.
11+
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.
12+
13+
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).
14+
15+
.. 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.
1216

1317

1418
.. contents:: Table of Contents

site/source/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.rst

+14-4
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ to prevent C++ name mangling.
6969
To compile this code run the following command in the Emscripten
7070
home directory::
7171

72-
./emcc tests/hello_function.cpp -o function.html -s EXPORTED_FUNCTIONS="['_int_sqrt']"
72+
./emcc tests/hello_function.cpp -o function.html -s EXPORTED_FUNCTIONS='["_int_sqrt"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]'
73+
74+
``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).
7375

7476
.. note::
7577

@@ -146,9 +148,17 @@ parameters to pass to the function:
146148
as the latter will force the method to actually be included in
147149
the build.
148150

149-
- Use ``Module.ccall`` and not ``ccall`` by itself. The former will work
150-
at all optimisation levels (even if the :term:`Closure Compiler`
151-
minifies the function names).
151+
- The compiler will remove code it does not see is used, to improve code
152+
size. If you use ``ccall`` in a place it sees, like code in a ``--pre-js``
153+
or ``--post-js``, it will just work. If you use it in a place the compiler
154+
didn't see, like another script tag on the HTML or in the JS console like
155+
we did in this tutorial, then because of optimizations
156+
and minification you should export ccall from the runtime, using
157+
``EXTRA_EXPORTED_RUNTIME_METHODS``, for example using
158+
``-s 'EXTRA_EXPORTED_RUNTIME_METHODS=["ccall", "cwrap"]'``,
159+
and call it on ``Module`` (which contains
160+
everything exported, in a safe way that is not influenced by minification
161+
or optimizations).
152162

153163

154164
Interacting with an API written in C/C++ from NodeJS

src/modules.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ function maybeExport(name) {
283283
// check if it already exists, to support EXPORT_ALL and other cases
284284
// (we could optimize this, but in ASSERTIONS mode code size doesn't
285285
// matter anyhow)
286-
return 'if (!Module["' + name + '"]) Module["' + name + '"] = function() { abort("\'' + name + '\' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS.") };';
286+
return 'if (!Module["' + name + '"]) Module["' + name + '"] = function() { abort("\'' + name + '\' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };';
287287
}
288288
return '';
289289
}

src/preamble.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ function Pointer_stringify(ptr, length) {
431431
}
432432
return ret;
433433
}
434-
return Module['UTF8ToString'](ptr);
434+
return UTF8ToString(ptr);
435435
}
436436
{{{ maybeExport('Pointer_stringify') }}}
437437

src/settings.js

-22
Original file line numberDiff line numberDiff line change
@@ -355,34 +355,12 @@ var EXPORTED_RUNTIME_METHODS = [ // Runtime elements that are exported on Module
355355
'FS_createDevice',
356356
'FS_unlink',
357357
'Runtime',
358-
'ccall',
359-
'cwrap',
360358
'ALLOC_NORMAL',
361359
'ALLOC_STACK',
362360
'ALLOC_STATIC',
363361
'ALLOC_DYNAMIC',
364362
'ALLOC_NONE',
365-
'allocate',
366363
'getMemory',
367-
'Pointer_stringify',
368-
'AsciiToString',
369-
'stringToAscii',
370-
'UTF8ArrayToString',
371-
'UTF8ToString',
372-
'stringToUTF8Array',
373-
'stringToUTF8',
374-
'lengthBytesUTF8',
375-
'stackTrace',
376-
'addOnPreRun',
377-
'addOnInit',
378-
'addOnPreMain',
379-
'addOnExit',
380-
'addOnPostRun',
381-
'intArrayFromString',
382-
'intArrayToString',
383-
'writeStringToMemory',
384-
'writeArrayToMemory',
385-
'writeAsciiToMemory',
386364
'addRunDependency',
387365
'removeRunDependency',
388366
];
+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
'setValue' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS.
1+
'setValue' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)

tests/fs/test_lz4fs.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ int main() {
147147
console.log('seeing compressed size of ' + compressedSize + ', expect in ' + [low, high]);
148148
assert(compressedSize > low && compressedSize < high); // more than 1/3, because 1/3 is uncompressible, but still, less than 1/2
149149

150-
Module['ccall']('finish');
150+
ccall('finish');
151151
}
152152

153153
var meta_xhr = new XMLHttpRequest();

tests/fs/test_workerfs_package.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ int main() {
5656
packages: [{ metadata: meta, blob: blob }]
5757
}, '/files');
5858

59-
Module.ccall('finish');
59+
ccall('finish');
6060
}
6161

6262
var meta_xhr = new XMLHttpRequest();

tests/test_browser.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -2155,7 +2155,7 @@ def test_runtime_misuse(self):
21552155
self.btest(filename, expected='606', args=['--post-js', 'post.js', '--memory-init-file', '0', '-s', 'NO_EXIT_RUNTIME=1'] + extra_args + mode)
21562156

21572157
def test_cwrap_early(self):
2158-
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')
2158+
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')
21592159

21602160
def test_worker_api(self):
21612161
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):
35323532
self.run_browser('test.html', '', '/report_result?0')
35333533

35343534
def test_utf8_textdecoder(self):
3535-
self.btest('benchmark_utf8.cpp', expected='0', args=['--embed-file', path_from_root('tests/utf8_corpus.txt') + '@/utf8_corpus.txt'])
3535+
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"]'])
35363536

35373537
def test_utf16_textdecoder(self):
35383538
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"]'])

tests/test_core.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -4417,7 +4417,7 @@ def test_utime(self):
44174417
def test_utf(self):
44184418
self.banned_js_engines = [SPIDERMONKEY_ENGINE] # only node handles utf well
44194419
Settings.EXPORTED_FUNCTIONS = ['_main', '_malloc']
4420-
Settings.EXTRA_EXPORTED_RUNTIME_METHODS = ['getValue', 'setValue']
4420+
Settings.EXTRA_EXPORTED_RUNTIME_METHODS = ['getValue', 'setValue', 'UTF8ToString', 'stringToUTF8']
44214421
self.do_run_in_out_file_test('tests', 'core', 'test_utf')
44224422

44234423
def test_utf32(self):
@@ -4426,10 +4426,12 @@ def test_utf32(self):
44264426
self.do_run(open(path_from_root('tests', 'utf32.cpp')).read(), 'OK.', args=['-fshort-wchar'])
44274427

44284428
def test_utf8(self):
4429+
Settings.EXTRA_EXPORTED_RUNTIME_METHODS = ['UTF8ToString', 'stringToUTF8', 'AsciiToString', 'stringToAscii']
44294430
Building.COMPILER_TEST_OPTS += ['-std=c++11']
44304431
self.do_run(open(path_from_root('tests', 'utf8.cpp')).read(), 'OK.')
44314432

44324433
def test_utf8_textdecoder(self):
4434+
Settings.EXTRA_EXPORTED_RUNTIME_METHODS = ['UTF8ToString', 'stringToUTF8']
44334435
Building.COMPILER_TEST_OPTS += ['--embed-file', path_from_root('tests/utf8_corpus.txt')+ '@/utf8_corpus.txt']
44344436
self.do_run(open(path_from_root('tests', 'benchmark_utf8.cpp')).read(), 'OK.')
44354437

@@ -5906,6 +5908,7 @@ def test_autodebug(self):
59065908

59075909
@sync
59085910
def test_ccall(self):
5911+
Settings.EXTRA_EXPORTED_RUNTIME_METHODS = ['ccall', 'cwrap']
59095912
post = '''
59105913
def process(filename):
59115914
src = open(filename, 'r').read() + \'\'\'
@@ -6855,10 +6858,10 @@ def test_exit_status(self):
68556858
}
68566859
'''
68576860
open('post.js', 'w').write('''
6858-
Module.addOnExit(function () {
6861+
addOnExit(function () {
68596862
Module.print('I see exit status: ' + EXITSTATUS);
68606863
});
6861-
Module.callMain();
6864+
Module['callMain']();
68626865
''')
68636866
self.emcc_args += ['-s', 'INVOKE_RUN=0', '--post-js', 'post.js']
68646867
self.do_run(src.replace('CAPITAL_EXIT', '0'), 'hello, world!\ncleanup\nI see exit status: 118')
@@ -6935,7 +6938,7 @@ def test_async(self):
69356938
Settings.INVOKE_RUN = 0
69366939
open('post.js', 'w').write('''
69376940
try {
6938-
Module['ccall']('main', 'number', ['number', 'string'], [2, 'waka']);
6941+
ccall('main', 'number', ['number', 'string'], [2, 'waka']);
69396942
var never = true;
69406943
} catch(e) {
69416944
Module.print(e);
@@ -6956,7 +6959,7 @@ def test_async(self):
69566959
}
69576960
'''
69586961
open('post.js', 'w').write('''
6959-
Module['ccall']('main', null, ['number', 'string'], [2, 'waka'], { async: true });
6962+
ccall('main', null, ['number', 'string'], [2, 'waka'], { async: true });
69606963
''')
69616964
self.do_run(src, 'HelloWorld');
69626965

tests/test_other.py

+10-11
Original file line numberDiff line numberDiff line change
@@ -2467,7 +2467,7 @@ def test_module_exports_with_closure(self):
24672467
self.clear()
24682468

24692469
# compile with -O2 --closure 0
2470-
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()
2470+
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()
24712471

24722472
# Check that compilation was successful
24732473
assert os.path.exists('test.js')
@@ -2822,6 +2822,7 @@ def test_proxyfs(self):
28222822
Popen([PYTHON, EMCC,
28232823
'-o', 'proxyfs_test.js', 'proxyfs_test.c',
28242824
'--embed-file', 'proxyfs_embed.txt', '--pre-js', 'proxyfs_pre.js',
2825+
'-s', 'EXTRA_EXPORTED_RUNTIME_METHODS=["ccall", "cwrap"]',
28252826
'-s', 'MAIN_MODULE=1']).communicate()
28262827
# Following shutil.copyfile just prevent 'require' of node.js from caching js-object.
28272828
# See https://nodejs.org/api/modules.html
@@ -4886,7 +4887,7 @@ def do(name, source, moar_opts):
48864887
assert sizes['no_nuthin'] < ratio*sizes['normal']
48874888
assert sizes['no_nuthin'] < absolute, str(sizes['no_nuthin']) + ' >= ' + str(absolute)
48884889
if '--closure' in opts: # no EXPORTED_RUNTIME_METHODS makes closure much more effective
4889-
assert sizes['no_nuthin'] < 0.975*sizes['no_fs']
4890+
assert sizes['no_nuthin'] < 0.995*sizes['no_fs']
48904891
assert sizes['no_fs_manual'] < sizes['no_fs'] # manual can remove a tiny bit more
48914892
test(['-s', 'ASSERTIONS=0'], 0.75, 360000) # we don't care about code size with assertions
48924893
test(['-O1'], 0.66, 210000)
@@ -4912,13 +4913,11 @@ def do(name, moar_opts):
49124913
assert sizes['no_nuthin'] < sizes['normal']
49134914
assert sizes['no_nuthin'] < ratio*sizes['normal']
49144915
assert sizes['no_nuthin'] < absolute
4915-
if '--closure' in opts: # no EXPORTED_RUNTIME_METHODS makes closure much more effective
4916-
assert sizes['no_nuthin'] < 0.975*sizes['normal']
49174916
test(['-s', 'ASSERTIONS=0'], 1, 220000) # we don't care about code size with assertions
49184917
test(['-O1'], 1, 215000)
4919-
test(['-O2'], 0.99, 75000)
4920-
test(['-O3', '--closure', '1'], 0.975, 50000)
4921-
test(['-O3', '--closure', '2'], 0.975, 41000) # might change now and then
4918+
test(['-O2'], 0.995, 55000)
4919+
test(['-O3', '--closure', '1'], 0.995, 38000)
4920+
test(['-O3', '--closure', '2'], 0.995, 35000) # might change now and then
49224921

49234922
def test_no_browser(self):
49244923
BROWSER_INIT = 'var Browser'
@@ -4941,10 +4940,10 @@ def test(opts, has, not_has):
49414940
self.assertContained(has, src)
49424941
self.assertNotContained(not_has, src)
49434942

4944-
test([], 'Module["intArray', 'Module["waka')
4945-
test(['-s', 'EXPORTED_RUNTIME_METHODS=[]'], '', 'Module["intArray')
4946-
test(['-s', 'EXPORTED_RUNTIME_METHODS=["intArrayToString"]'], 'Module["intArray', 'Module["waka')
4947-
test(['-s', 'EXPORTED_RUNTIME_METHODS=[]', '-s', 'EXTRA_EXPORTED_RUNTIME_METHODS=["intArrayToString"]'], 'Module["intArray', 'Module["waka')
4943+
test([], 'Module["getMemory', 'Module["waka')
4944+
test(['-s', 'EXPORTED_RUNTIME_METHODS=[]'], '', 'Module["getMemory')
4945+
test(['-s', 'EXPORTED_RUNTIME_METHODS=["getMemory"]'], 'Module["getMemory', 'Module["waka')
4946+
test(['-s', 'EXPORTED_RUNTIME_METHODS=[]', '-s', 'EXTRA_EXPORTED_RUNTIME_METHODS=["getMemory"]'], 'Module["getMemory', 'Module["waka')
49484947

49494948
def test_stat_fail_alongtheway(self):
49504949
open('src.cpp', 'w').write(r'''

tools/ports/sdl.py

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def process_args(ports, args, settings, shared):
5656
elif settings.USE_SDL == 2:
5757
get(ports, settings, shared)
5858
args += ['-Xclang', '-isystem' + os.path.join(shared.Cache.get_path('ports-builds'), 'sdl2', 'include')]
59+
settings.EXPORTED_RUNTIME_METHODS.append('Pointer_stringify')
5960
return args
6061

6162
def show():

0 commit comments

Comments
 (0)