Skip to content

Commit b6d09b0

Browse files
authored
Add EXPORT_KEEPALIVE to export symbols even in MINIMAL_RUNTIME (#18819)
1 parent 7cd514d commit b6d09b0

File tree

5 files changed

+66
-3
lines changed

5 files changed

+66
-3
lines changed

ChangeLog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ See docs/process.md for more on how version tagging works.
2424
- Update glfw header to 3.3.8 (#18826)
2525
- The `LLD_REPORT_UNDEFINED` setting has been removed. It's now essentially
2626
always enabled. (#18342)
27+
- Added `-sEXPORT_KEEPALIVE` to export symbols. When using
28+
`MINIMAL_RUNTIME`, the option will be **disabled** by default.
29+
This option simply exports the symbols on the module object, i.e.,
30+
`Module['X'] = X;`
2731

2832
3.1.32 - 02/17/23
2933
-----------------

emcc.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1959,6 +1959,8 @@ def phase_linker_setup(options, state, newargs):
19591959
default_setting('SUPPORT_ERRNO', 0)
19601960
# Require explicit -lfoo.js flags to link with JS libraries.
19611961
default_setting('AUTO_JS_LIBRARIES', 0)
1962+
# When using MINIMAL_RUNTIME, symbols should only be exported if requested.
1963+
default_setting('EXPORT_KEEPALIVE', 0)
19621964

19631965
if settings.STRICT_JS and (settings.MODULARIZE or settings.EXPORT_ES6):
19641966
exit_with_error("STRICT_JS doesn't work with MODULARIZE or EXPORT_ES6")

emscripten.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -740,7 +740,8 @@ def install_wrapper(sym):
740740
wrapper = '/** @type {function(...*):?} */\nvar %s = ' % mangled
741741

742742
# TODO(sbc): Can we avoid exporting the dynCall_ functions on the module.
743-
if mangled in settings.EXPORTED_FUNCTIONS or name.startswith('dynCall_'):
743+
should_export = settings.EXPORT_KEEPALIVE and mangled in settings.EXPORTED_FUNCTIONS
744+
if name.startswith('dynCall_') or should_export:
744745
exported = 'Module["%s"] = ' % mangled
745746
else:
746747
exported = ''
@@ -792,8 +793,9 @@ def create_receiving(exports):
792793
for s in exports_that_are_not_initializers:
793794
mangled = asmjs_mangle(s)
794795
dynCallAssignment = ('dynCalls["' + s.replace('dynCall_', '') + '"] = ') if generate_dyncall_assignment and mangled.startswith('dynCall_') else ''
796+
should_export = settings.EXPORT_ALL or (settings.EXPORT_KEEPALIVE and mangled in settings.EXPORTED_FUNCTIONS)
795797
export_assignment = ''
796-
if settings.MODULARIZE and settings.EXPORT_ALL:
798+
if settings.MODULARIZE and should_export:
797799
export_assignment = f'Module["{mangled}"] = '
798800
receiving += [f'{export_assignment}{dynCallAssignment}{mangled} = asm["{s}"]']
799801
else:

src/settings.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,11 @@ var EXPORTED_FUNCTIONS = [];
994994
// [link]
995995
var EXPORT_ALL = false;
996996

997+
// If true, we export the symbols that are present in JS onto the Module
998+
// object.
999+
// It only does Module['X'] = X;
1000+
var EXPORT_KEEPALIVE = true;
1001+
9971002
// Remembers the values of these settings, and makes them accessible
9981003
// through getCompilerSetting and emscripten_get_compiler_setting.
9991004
// To see what is retained, look for compilerSettings in the generated code.
@@ -1824,7 +1829,10 @@ var SUPPORT_ERRNO = true;
18241829
// MINIMAL_RUNTIME=2 to further enable even more code size optimizations. These
18251830
// opts are quite hacky, and work around limitations in Closure and other parts
18261831
// of the build system, so they may not work in all generated programs (But can
1827-
// be useful for really small programs)
1832+
// be useful for really small programs).
1833+
//
1834+
// By default, no symbols will be exported on the `Module` object. In order
1835+
// to export kept alive symbols, please use `-sEXPORT_KEEPALIVE=1`.
18281836
// [link]
18291837
var MINIMAL_RUNTIME = 0;
18301838

test/test_other.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,53 @@ def test_export_all(self):
13071307
self.emcc('lib.c', ['-Oz', '-sEXPORT_ALL', '-sLINKABLE', '--pre-js', 'main.js'], output_filename='a.out.js')
13081308
self.assertContained('libf1\nlibf2\n', self.run_js('a.out.js'))
13091309

1310+
def test_export_keepalive(self):
1311+
create_file('main.c', r'''
1312+
#include <emscripten.h>
1313+
EMSCRIPTEN_KEEPALIVE int libf1() { return 42; }
1314+
''')
1315+
1316+
create_file('pre.js', '''
1317+
Module.onRuntimeInitialized = () => {
1318+
console.log(Module._libf1 ? Module._libf1() : 'unexported');
1319+
};
1320+
''')
1321+
1322+
# By default, all kept alive functions should be exported.
1323+
self.do_runf('main.c', '42\n', emcc_args=['--pre-js', 'pre.js'])
1324+
1325+
# Ensures that EXPORT_KEEPALIVE=0 remove the exports
1326+
self.do_runf('main.c', 'unexported\n', emcc_args=['-sEXPORT_KEEPALIVE=0', '--pre-js', 'pre.js'])
1327+
1328+
def test_minimal_modularize_export_keepalive(self):
1329+
self.set_setting('MODULARIZE')
1330+
self.set_setting('MINIMAL_RUNTIME')
1331+
1332+
create_file('main.c', r'''
1333+
#include <emscripten.h>
1334+
EMSCRIPTEN_KEEPALIVE int libf1() { return 42; }
1335+
''')
1336+
1337+
def write_js_main():
1338+
"""
1339+
With MINIMAL_RUNTIME, the module instantiation function isn't exported neither as a UMD nor as an ES6 module.
1340+
Thus, it's impossible to use `require` or `import`.
1341+
1342+
This function simply appends the instantiation code to the generated code.
1343+
"""
1344+
runtime = read_file('test.js')
1345+
write_file('main.js', f'{runtime}\nModule().then((mod) => console.log(mod._libf1()));')
1346+
1347+
# By default, no symbols should be exported when using MINIMAL_RUNTIME.
1348+
self.emcc('main.c', [], output_filename='test.js')
1349+
write_js_main()
1350+
self.assertContained('TypeError: mod._libf1 is not a function', self.run_js('main.js', assert_returncode=NON_ZERO))
1351+
1352+
# Ensures that EXPORT_KEEPALIVE=1 exports the symbols.
1353+
self.emcc('main.c', ['-sEXPORT_KEEPALIVE=1'], output_filename='test.js')
1354+
write_js_main()
1355+
self.assertContained('42\n', self.run_js('main.js'))
1356+
13101357
def test_minimal_runtime_export_all_modularize(self):
13111358
"""This test ensures that MODULARIZE and EXPORT_ALL work simultaneously.
13121359

0 commit comments

Comments
 (0)