Skip to content
Open
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
10 changes: 10 additions & 0 deletions site/source/docs/tools_reference/settings_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1856,6 +1856,16 @@ used. An example of using the module is below.
foo();
bar();

The ``init`` function exists so the caller can configure the instance (via
``moduleArg``) before it starts. If there is nothing to configure (i.e.
``INCOMING_MODULE_JS_API`` is empty, as implied by ``STRICT``) the module
instead self-initializes via top-level await and ``init`` is not exported;
the named Wasm/runtime exports are ready to use as soon as it is imported::

import { foo, bar } from "./my_module.mjs"
foo();
bar();

Default value: false

.. _export_es6:
Expand Down
18 changes: 18 additions & 0 deletions src/postamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,19 @@ var wasmRawExports;
#if ASSERTIONS
var initCalled = false;
#endif
#if INCOMING_MODULE_JS_API.size == 0 && !WASM_ESM_INTEGRATION
Comment thread
guybedford marked this conversation as resolved.
// With no configuration points, `init` is not exported; we self-initialize below.
async function init() {
#else
export default async function init(moduleArg = {}) {
#endif
#if ASSERTIONS
assert(!initCalled);
initCalled = true;
#endif
#if INCOMING_MODULE_JS_API.size != 0 || WASM_ESM_INTEGRATION
Object.assign(Module, moduleArg);
#endif
processModuleArgs();
#if WASM_ESM_INTEGRATION
#if PTHREADS
Expand All @@ -276,6 +283,15 @@ export default async function init(moduleArg = {}) {
await run();
}

#if INCOMING_MODULE_JS_API.size == 0 && !WASM_ESM_INTEGRATION
#if PTHREADS || WASM_WORKERS
// Worker threads initialize on demand, so only the main thread inits here.
if ({{{ ENVIRONMENT_IS_MAIN_THREAD() }}})
#endif
await init();

#else

#if ENVIRONMENT_MAY_BE_NODE
// When run as the main script under node we run `init` immediately.
if (ENVIRONMENT_IS_NODE
Expand All @@ -297,6 +313,8 @@ if (ENVIRONMENT_IS_SHELL) {
}
#endif

#endif

#else // MODULARIZE == instance

#if WASM_WORKERS || PTHREADS
Expand Down
2 changes: 1 addition & 1 deletion src/preamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ function initRuntime() {
#endif

#if PTHREADS
if (ENVIRONMENT_IS_PTHREAD) return startWorker();
if (ENVIRONMENT_IS_PTHREAD) return;
#endif

#if STACK_OVERFLOW_CHECK >= 2
Expand Down
7 changes: 5 additions & 2 deletions src/pthread_esm_startup.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,17 @@ self.onmessage = async (msg) => {

// Now that we have the wasmMemory we can import the main program
globalThis.wasmMemory = msg.data.wasmMemory;

const prog = await import('./{{{ TARGET_JS_NAME }}}');

#if INCOMING_MODULE_JS_API.size != 0
await prog.default()
#endif

// Now that the import is completed the main program will have installed
// its own `onmessage` handler and replaced our handler.
// Now we can dispatch any queued messages to this new handler.
for (const msg of messageQueue) {
await self.onmessage(msg);
}

await prog.default()
};
3 changes: 3 additions & 0 deletions src/runtime_pthread.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ if (ENVIRONMENT_IS_PTHREAD) {
run();
#endif
#endif // MINIMAL_RUNTIME
#endif
#if !MINIMAL_RUNTIME
startWorker();
#endif
} else if (cmd == {{{ CMD_RUN }}}) {
#if ASSERTIONS
Expand Down
10 changes: 10 additions & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,16 @@ var SMALL_XHR_CHUNKS = false;
// foo();
// bar();
//
// The ``init`` function exists so the caller can configure the instance (via
// ``moduleArg``) before it starts. If there is nothing to configure (i.e.
// ``INCOMING_MODULE_JS_API`` is empty, as implied by ``STRICT``) the module
// instead self-initializes via top-level await and ``init`` is not exported;
// the named Wasm/runtime exports are ready to use as soon as it is imported::
//
// import { foo, bar } from "./my_module.mjs"
// foo();
// bar();
//
// [link]
var MODULARIZE = false;

Expand Down
8 changes: 4 additions & 4 deletions test/codesize/test_codesize_minimal_pthreads.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"a.out.js": 6954,
"a.out.js.gz": 3428,
"a.out.js": 6945,
"a.out.js.gz": 3424,
"a.out.nodebug.wasm": 19063,
"a.out.nodebug.wasm.gz": 8803,
"total": 26017,
"total_gz": 12231,
"total": 26008,
"total_gz": 12227,
"sent": [
"a (memory)",
"b (exit)",
Expand Down
8 changes: 4 additions & 4 deletions test/codesize/test_codesize_minimal_pthreads_memgrowth.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"a.out.js": 7362,
"a.out.js.gz": 3631,
"a.out.js": 7353,
"a.out.js.gz": 3622,
"a.out.nodebug.wasm": 19064,
"a.out.nodebug.wasm.gz": 8804,
"total": 26426,
"total_gz": 12435,
"total": 26417,
"total_gz": 12426,
"sent": [
"a (memory)",
"b (exit)",
Expand Down
67 changes: 67 additions & 0 deletions test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -9831,6 +9831,73 @@ def test_modularize_instance(self):

self.assertContained('main1\nmain2\nfoo\nbar\nbaz\n', self.run_js('runner.mjs'))

@no_omit_asm_module_exports('MODULARIZE is not compatible with DECLARE_ASM_MODULE_EXPORTS=0')
@no_strict_js('EXPORT_ES6 is not compatible with STRICT_JS')
def test_modularize_instance_tla(self):
create_file('library.js', '''\
addToLibrary({
$baz: () => console.log('baz'),
$qux: () => console.log('qux'),
});''')
# An empty INCOMING_MODULE_JS_API means there is nothing to configure, so the
# module self-initializes via top-level await and does not export `init`.
self.run_process([EMCC, test_file('modularize_instance.c'),
'-sMODULARIZE=instance', '-sINCOMING_MODULE_JS_API=[]',
'-Wno-experimental',
'-sEXPORTED_RUNTIME_METHODS=baz,addOnExit,HEAP32',
'-sEXPORTED_FUNCTIONS=_bar,_main,qux',
'--js-library', 'library.js',
'-o', 'modularize_instance.mjs'] + self.get_cflags())

# Named exports are usable directly on import; there is no `init` export.
create_file('runner.mjs', '''
import { strict as assert } from 'assert';
import * as mod from "./modularize_instance.mjs";
import { _foo as foo, _bar as bar, baz, qux, addOnExit, HEAP32 } from "./modularize_instance.mjs";
assert(mod.default === undefined); // no `init` export when self-initializing
foo(); // exported with EMSCRIPTEN_KEEPALIVE
bar(); // exported with EXPORTED_FUNCTIONS
baz(); // exported library function with EXPORTED_RUNTIME_METHODS
qux(); // exported library function with EXPORTED_FUNCTIONS
assert(typeof addOnExit === 'function'); // exported runtime function with EXPORTED_RUNTIME_METHODS
assert(typeof HEAP32 === 'object'); // exported runtime value by default
''')

self.assertContained('main1\nmain2\nfoo\nbar\nbaz\n', self.run_js('runner.mjs'))

@no_omit_asm_module_exports('MODULARIZE is not compatible with DECLARE_ASM_MODULE_EXPORTS=0')
@no_strict_js('EXPORT_ES6 is not compatible with STRICT_JS')
@also_with_pthreads
@esm_integration
def test_esm_integration_tla(self):
self.set_setting('INCOMING_MODULE_JS_API', [])
create_file('library.js', '''\
addToLibrary({
$baz: () => console.log('baz'),
$qux: () => console.log('qux'),
});''')
self.run_process([EMCC, test_file('modularize_instance.c'),
'-Wno-experimental',
'-sEXPORTED_RUNTIME_METHODS=baz,addOnExit,HEAP32',
'-sEXPORTED_FUNCTIONS=_bar,_main,qux',
'--js-library', 'library.js',
'-o', 'modularize_instance.mjs'] + self.get_cflags())

create_file('runner.mjs', '''
import { strict as assert } from 'assert';
import * as mod from "./modularize_instance.mjs";
import { _foo as foo, _bar as bar, baz, qux, addOnExit, HEAP32 } from "./modularize_instance.mjs";
assert(mod.default === undefined); // no `init` export when self-initializing
foo();
bar();
baz();
qux();
assert(typeof addOnExit === 'function');
assert(typeof HEAP32 === 'object');
''')

self.assertContained('main1\nmain2\nfoo\nbar\nbaz\n', self.run_js('runner.mjs'))

@no_omit_asm_module_exports('MODULARIZE is not compatible with DECLARE_ASM_MODULE_EXPORTS=0')
@no_4gb('EMBIND_AOT can\'t lower 4gb')
@no_strict_js('EXPORT_ES6 is not compatible with STRICT_JS')
Expand Down
20 changes: 14 additions & 6 deletions tools/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -2145,13 +2145,14 @@ def create_esm_wrapper(wrapper_file, support_target, wasm_target):
wrapper.append('// in order to avoid issues with circular dependencies.')
wrapper.append(f"import * as unused from './{settings.WASM_BINARY_FILE}';")
support_url = f'./{os.path.basename(support_target)}'
if js_exports:
wrapper.append(f"export {{ default, {js_exports} }} from '{support_url}';")
else:
wrapper.append(f"export {{ default }} from '{support_url}';")
if settings.INCOMING_MODULE_JS_API:
if js_exports:
wrapper.append(f"export {{ default, {js_exports} }} from '{support_url}';")
else:
wrapper.append(f"export {{ default }} from '{support_url}';")

if settings.ENVIRONMENT_MAY_BE_NODE:
wrapper.append(f'''
if settings.ENVIRONMENT_MAY_BE_NODE:
wrapper.append(f'''
// When run as the main module under node, create the module directly. This will
// execute any startup code along with main (if it exists).
import init from '{support_url}';
Expand All @@ -2161,6 +2162,13 @@ def create_esm_wrapper(wrapper_file, support_target, wasm_target):
const isMainModule = url.pathToFileURL(process.argv[1]).href === import.meta.url;
if (isMainModule) await init();
}}''')
else:
# With no configuration points, self-initialize via top-level await and
# don't re-export `init`.
if js_exports:
wrapper.append(f"export {{ {js_exports} }} from '{support_url}';")
wrapper.append(f"import init from '{support_url}';")
wrapper.append('await init();')

write_file(wrapper_file, '\n'.join(wrapper) + '\n')

Expand Down
Loading