Skip to content

Commit 58dbca2

Browse files
committed
Defer emitting JS library aliases when target is in a dependency cycle
When a JS library alias target (e.g. `foo`) is visited first and transitively depends on its own alias (e.g. `foo_alias: 'foo'`), `addedLibraryItems['foo']` is marked as visited before `var _foo = ...` is emitted. Without deferral, `addFromLibrary('foo_alias')` emits `var _foo_alias = _foo;` before `var _foo = ...`, leaving `_foo_alias` initialized to `undefined` for arrow functions and non-hoisted values. Track emitted library symbols in `emittedLibraryItems` and defer alias assignments in `deferredAliases` until immediately after their target symbol is emitted.
1 parent 119421b commit 58dbca2

2 files changed

Lines changed: 30 additions & 3 deletions

File tree

‎src/jsifier.mjs‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ import {
4545
import {extraExports, LibraryManager, librarySymbols, nativeAliases} from './modules.mjs';
4646

4747
const addedLibraryItems = {};
48+
const emittedLibraryItems = new Set();
49+
const deferredAliases = {};
4850

4951
// Experimental feature to check for invalid __deps entries.
5052
// See `EMCC_CHECK_DEPS` in in the environment to try it out.
@@ -728,11 +730,12 @@ function(${args}) {
728730
}
729731
}
730732

733+
let aliasTarget;
731734
if (LibraryManager.isAlias(snippet)) {
732735
// Redirection for aliases. We include the parent, and at runtime
733736
// make ourselves equal to it. This avoid having duplicate
734737
// functions with identical content.
735-
const aliasTarget = snippet;
738+
aliasTarget = snippet;
736739
if (WASM_EXPORTS.has(aliasTarget)) {
737740
debugLog(`native alias: ${mangled} -> ${aliasTarget}`);
738741
nativeAliases[mangled] = aliasTarget;
@@ -884,7 +887,23 @@ function(${args}) {
884887
.filter((x) => x != '')
885888
.join('\n') + '\n'
886889
: '';
887-
return depsText + commentText + contentText;
890+
if (aliasTarget && !isNativeAlias && !isFunction && !emittedLibraryItems.has(aliasTarget)) {
891+
// If `aliasTarget` is not yet emitted, we are in a dependency cycle
892+
// where `aliasTarget` is an ancestor on the recursion stack. Defer
893+
// emitting `var _alias = _target;` until immediately after `_target`
894+
// is defined so `_target` is not referenced before its initialization.
895+
(deferredAliases[aliasTarget] ??= []).push({symbol, text: commentText + contentText});
896+
return depsText.trim();
897+
}
898+
function flushDeferred(sym) {
899+
emittedLibraryItems.add(sym);
900+
let res = '';
901+
for (const item of deferredAliases[sym] ?? []) {
902+
res += '\n' + item.text + flushDeferred(item.symbol);
903+
}
904+
return res;
905+
}
906+
return depsText + commentText + contentText + flushDeferred(symbol);
888907
}
889908

890909
const JS = addFromLibrary(symbol, 'root reference (e.g. compiled C/C++ code)');

‎test/test_jslib.py‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,15 @@ def test_jslib_include(self):
604604
def test_jslib_aliases(self, args):
605605
create_file('foo.js', '''
606606
addToLibrary({
607-
foo: () => 42,
607+
// Create a dependency cycle (foo -> $call_alias -> foo_alias -> foo)
608+
// to test that `var _foo_alias = _foo;` is still emitted after `_foo`.
609+
$call_alias__deps: ['foo_alias'],
610+
$call_alias: () => _foo_alias(),
611+
foo__deps: ['$call_alias'],
612+
foo: () => {
613+
if (!call_alias) return 0;
614+
return 42;
615+
},
608616
foo__sig: 'i',
609617
610618
foo_alias: 'foo',

0 commit comments

Comments
 (0)