Skip to content
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
4 changes: 4 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ See docs/process.md for more on how version tagging works.
- Update glfw header to 3.3.8 (#18826)
- The `LLD_REPORT_UNDEFINED` setting has been removed. It's now essentially
always enabled. (#18342)
- Added `-sEXPORT_KEEPALIVE` to export symbols. When using
`MINIMAL_RUNTIME`, the option will be **disabled** by default.
This option simply exports the symbols on the module object, i.e.,
`Module['X'] = X;`

3.1.32 - 02/17/23
-----------------
Expand Down
2 changes: 2 additions & 0 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1959,6 +1959,8 @@ def phase_linker_setup(options, state, newargs):
default_setting('SUPPORT_ERRNO', 0)
# Require explicit -lfoo.js flags to link with JS libraries.
default_setting('AUTO_JS_LIBRARIES', 0)
# When using MINIMAL_RUNTIME, symbols should only be exported if requested.
default_setting('EXPORT_KEEPALIVE', 0)

if settings.STRICT_JS and (settings.MODULARIZE or settings.EXPORT_ES6):
exit_with_error("STRICT_JS doesn't work with MODULARIZE or EXPORT_ES6")
Expand Down
6 changes: 4 additions & 2 deletions emscripten.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,8 @@ def install_wrapper(sym):
wrapper = '/** @type {function(...*):?} */\nvar %s = ' % mangled

# TODO(sbc): Can we avoid exporting the dynCall_ functions on the module.
if mangled in settings.EXPORTED_FUNCTIONS or name.startswith('dynCall_'):
should_export = settings.EXPORT_KEEPALIVE and mangled in settings.EXPORTED_FUNCTIONS
if name.startswith('dynCall_') or should_export:
exported = 'Module["%s"] = ' % mangled
else:
exported = ''
Expand Down Expand Up @@ -792,8 +793,9 @@ def create_receiving(exports):
for s in exports_that_are_not_initializers:
mangled = asmjs_mangle(s)
dynCallAssignment = ('dynCalls["' + s.replace('dynCall_', '') + '"] = ') if generate_dyncall_assignment and mangled.startswith('dynCall_') else ''
should_export = settings.EXPORT_ALL or (settings.EXPORT_KEEPALIVE and mangled in settings.EXPORTED_FUNCTIONS)
export_assignment = ''
if settings.MODULARIZE and settings.EXPORT_ALL:
if settings.MODULARIZE and should_export:
export_assignment = f'Module["{mangled}"] = '
receiving += [f'{export_assignment}{dynCallAssignment}{mangled} = asm["{s}"]']
else:
Expand Down
10 changes: 9 additions & 1 deletion src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,11 @@ var EXPORTED_FUNCTIONS = [];
// [link]
var EXPORT_ALL = false;

// If true, we export the symbols that are present in JS onto the Module
// object.
// It only does Module['X'] = X;
var EXPORT_KEEPALIVE = true;

// Remembers the values of these settings, and makes them accessible
// through getCompilerSetting and emscripten_get_compiler_setting.
// To see what is retained, look for compilerSettings in the generated code.
Expand Down Expand Up @@ -1824,7 +1829,10 @@ var SUPPORT_ERRNO = true;
// MINIMAL_RUNTIME=2 to further enable even more code size optimizations. These
// opts are quite hacky, and work around limitations in Closure and other parts
// of the build system, so they may not work in all generated programs (But can
// be useful for really small programs)
// be useful for really small programs).
//
// By default, no symbols will be exported on the `Module` object. In order
// to export kept alive symbols, please use `-sEXPORT_KEEPALIVE=1`.
// [link]
var MINIMAL_RUNTIME = 0;

Expand Down
47 changes: 47 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -1307,6 +1307,53 @@ def test_export_all(self):
self.emcc('lib.c', ['-Oz', '-sEXPORT_ALL', '-sLINKABLE', '--pre-js', 'main.js'], output_filename='a.out.js')
self.assertContained('libf1\nlibf2\n', self.run_js('a.out.js'))

def test_export_keepalive(self):
create_file('main.c', r'''
#include <emscripten.h>
EMSCRIPTEN_KEEPALIVE int libf1() { return 42; }
''')

create_file('pre.js', '''
Module.onRuntimeInitialized = () => {
console.log(Module._libf1 ? Module._libf1() : 'unexported');
};
''')

# By default, all kept alive functions should be exported.
self.do_runf('main.c', '42\n', emcc_args=['--pre-js', 'pre.js'])

# Ensures that EXPORT_KEEPALIVE=0 remove the exports
self.do_runf('main.c', 'unexported\n', emcc_args=['-sEXPORT_KEEPALIVE=0', '--pre-js', 'pre.js'])

def test_minimal_modularize_export_keepalive(self):
self.set_setting('MODULARIZE')
self.set_setting('MINIMAL_RUNTIME')

create_file('main.c', r'''
#include <emscripten.h>
EMSCRIPTEN_KEEPALIVE int libf1() { return 42; }
''')

def write_js_main():
"""
With MINIMAL_RUNTIME, the module instantiation function isn't exported neither as a UMD nor as an ES6 module.
Thus, it's impossible to use `require` or `import`.

This function simply appends the instantiation code to the generated code.
"""
runtime = read_file('test.js')
write_file('main.js', f'{runtime}\nModule().then((mod) => console.log(mod._libf1()));')

# By default, no symbols should be exported when using MINIMAL_RUNTIME.
self.emcc('main.c', [], output_filename='test.js')
write_js_main()
self.assertContained('TypeError: mod._libf1 is not a function', self.run_js('main.js', assert_returncode=NON_ZERO))

# Ensures that EXPORT_KEEPALIVE=1 exports the symbols.
self.emcc('main.c', ['-sEXPORT_KEEPALIVE=1'], output_filename='test.js')
write_js_main()
self.assertContained('42\n', self.run_js('main.js'))

def test_minimal_runtime_export_all_modularize(self):
"""This test ensures that MODULARIZE and EXPORT_ALL work simultaneously.

Expand Down