/** * @license * Copyright 2010 The Emscripten Authors * SPDX-License-Identifier: MIT */ // Convert analyzed data to javascript. Everything has already been calculated // before this stage, which just does the final conversion to JavaScript. import assert from 'node:assert'; import * as fs from 'node:fs/promises'; import { ATMODULES, ATEXITS, ATINITS, ATPOSTCTORS, ATPRERUNS, ATMAINS, ATPOSTRUNS, defineI64Param, indentify, makeReturn64, modifyJSFunction, preprocess, processMacros, receiveI64ParamAsI53, } from './parseTools.mjs'; import { addToCompileTimeContext, debugLog, error, errorOccured, isDecorator, isJsOnlySymbol, compileTimeContext, readFile, runInMacroContext, warn, warnOnce, warningOccured, localFile, timer, } from './utility.mjs'; import {LibraryManager, librarySymbols, nativeAliases} from './modules.mjs'; const addedLibraryItems = {}; const extraLibraryFuncs = []; // Experimental feature to check for invalid __deps entries. // See `EMCC_CHECK_DEPS` in in the environment to try it out. const CHECK_DEPS = process.env.EMCC_CHECK_DEPS; // Some JS-implemented library functions are proxied to be called on the main // browser thread, if the Emscripten runtime is executing in a Web Worker. // Each such proxied function is identified via an ordinal number (this is not // the same namespace as function pointers in general). const proxiedFunctionTable = []; // Mangles the given C/JS side function name to assembly level function name (adds an underscore) function mangleCSymbolName(f) { if (f === '__main_argc_argv') { f = 'main'; } return f[0] == '$' ? f.slice(1) : '_' + f; } // Splits out items that pass filter. Returns also the original sans the filtered function splitter(array, filter) { const splitOut = array.filter(filter); const leftIn = array.filter((x) => !filter(x)); return {leftIn, splitOut}; } function escapeJSONKey(x) { if (/^[\d\w_]+$/.exec(x) || x[0] === '"' || x[0] === "'") return x; assert(!x.includes("'"), 'cannot have internal single quotes in keys: ' + x); return "'" + x + "'"; } // JSON.stringify will completely omit function objects. This function is // similar but preserves functions. function stringifyWithFunctions(obj) { if (typeof obj == 'function') return obj.toString(); if (obj === null || typeof obj != 'object') return JSON.stringify(obj); if (Array.isArray(obj)) { return '[' + obj.map(stringifyWithFunctions).join(',') + ']'; } // preserve the type of the object if it is one of [Map, Set, WeakMap, WeakSet]. const builtinContainers = runInMacroContext('[Map, Set, WeakMap, WeakSet]', { filename: '', }); for (const container of builtinContainers) { if (obj instanceof container) { const className = container.name; assert(!obj.size, `cannot stringify ${className} with data`); return `new ${className}`; } } var rtn = '{\n'; for (const [key, value] of Object.entries(obj)) { var str = stringifyWithFunctions(value); // Handle JS method syntax where the function property starts with its own // name. e.g. `foo(a) {}` (or `async foo(a) {}`) if (typeof value === 'function' && (str.startsWith(key) || str.startsWith('async ' + key))) { rtn += str + ',\n'; } else { rtn += `${escapeJSONKey(key)}:${str},\n`; } } return rtn + '}'; } function isDefined(symName) { if (WASM_EXPORTS.has(symName) || SIDE_MODULE_EXPORTS.has(symName)) { return true; } if (symName == '__main_argc_argv' && SIDE_MODULE_EXPORTS.has('main')) { return true; } // 'invoke_' symbols are created at runtime in library_dylink.py so can // always be considered as defined. if (MAIN_MODULE && symName.startsWith('invoke_')) { return true; } return false; } function getTransitiveDeps(symbol) { // TODO(sbc): Use some kind of cache to avoid quadratic behaviour here. const transitiveDeps = new Set(); const seen = new Set(); const toVisit = [symbol]; while (toVisit.length) { const sym = toVisit.pop(); if (!seen.has(sym)) { let directDeps = LibraryManager.library[sym + '__deps'] || []; directDeps = directDeps.filter((d) => typeof d === 'string'); for (const dep of directDeps) { if (!transitiveDeps.has(dep)) { debugLog(`adding dependency ${symbol} -> ${dep}`); } transitiveDeps.add(dep); toVisit.push(dep); } seen.add(sym); } } return Array.from(transitiveDeps); } function shouldPreprocess(fileName) { var content = readFile(fileName).trim(); return content.startsWith('#preprocess\n') || content.startsWith('#preprocess\r\n'); } function getIncludeFile(fileName, alwaysPreprocess, shortName) { shortName ??= fileName; let result = `// include: ${shortName}\n`; const doPreprocess = alwaysPreprocess || shouldPreprocess(fileName); if (doPreprocess) { result += processMacros(preprocess(fileName), fileName); } else { result += readFile(fileName); } result += `// end include: ${shortName}\n`; return result; } function getSystemIncludeFile(fileName) { return getIncludeFile(localFile(fileName), /*alwaysPreprocess=*/ true, /*shortName=*/ fileName); } function preJS() { let result = ''; for (const fileName of PRE_JS_FILES) { result += getIncludeFile(fileName); } return result; } // Certain library functions have specific indirect dependencies. See the // comments alongside eaach of these. const checkDependenciesSkip = new Set([ '_mmap_js', '_emscripten_throw_longjmp', '_emscripten_receive_on_main_thread_js', 'emscripten_start_fetch', 'emscripten_start_wasm_audio_worklet_thread_async', ]); const checkDependenciesIgnore = new Set([ // These are added in bulk to whole library files are so are not precise '$PThread', '$SDL', '$GLUT', '$GLEW', '$Browser', '$AL', '$GL', '$IDBStore', // These are added purely for their side effects '$polyfillWaitAsync', '$GLImmediateSetup', '$emscriptenGetAudioObject', // These get conservatively injected via i53ConversionDeps '$bigintToI53Checked', '$convertI32PairToI53Checked', 'setTempRet0', ]); /** * Hacky attempt to find unused `__deps` entries. This is not enabled by default * but can be enabled by setting CHECK_DEPS above. * TODO: Use a more precise method such as tokenising using acorn. */ function checkDependencies(symbol, snippet, deps, postset) { if (checkDependenciesSkip.has(symbol)) { return; } for (const dep of deps) { if (typeof dep === 'function') { continue; } if (checkDependenciesIgnore.has(dep)) { continue; } const mangled = mangleCSymbolName(dep); if (!snippet.includes(mangled) && (!postset || !postset.includes(mangled))) { error(`${symbol}: unused dependency: ${dep}`); } } } function addImplicitDeps(snippet, deps) { // There are some common dependencies that we inject automatically by // conservatively scanning the input functions for their usage. // Specifically, these are dependencies that are very common and would be // burdensome to add manually to all functions. // The first four are deps that are automatically/conditionally added // by the {{{ makeDynCall }}}, and {{{ runtimeKeepalivePush/Pop }}} macros. const autoDeps = [ 'getDynCaller', 'getWasmTableEntry', 'runtimeKeepalivePush', 'runtimeKeepalivePop', 'UTF8ToString', ]; for (const dep of autoDeps) { if (snippet.includes(dep + '(')) { deps.push('$' + dep); } } } function sigToArgs(sig) { const args = [] for (var i = 1; i < sig.length; i++) { args.push(`a${i}`); } return args.join(','); } function handleI64Signatures(symbol, snippet, sig, i53abi, isAsyncFunction) { // Handle i64 parameters and return values. // // When WASM_BIGINT is enabled these arrive as BigInt values which we // convert to int53 JS numbers. If necessary, we also convert the return // value back into a BigInt. // // When WASM_BIGINT is not enabled we receive i64 values as a pair of i32 // numbers which is converted to single int53 number. In necessary, we also // split the return value into a pair of i32 numbers. return modifyJSFunction(snippet, (args, body, async_, oneliner) => { let argLines = args.split('\n'); argLines = argLines.map((line) => line.split('//')[0]); const argNames = argLines .join(' ') .split(',') .map((name) => name.trim()); const newArgs = []; let argConversions = ''; if (sig.length > argNames.length + 1) { error(`handleI64Signatures: signature '${sig}' too long for ${symbol}(${argNames.join(', ')})`); return snippet; } for (const [i, name] of argNames.entries()) { // If sig is shorter than argNames list then argType will be undefined // here, which will result in the default case below. const argType = sig[i + 1]; if (WASM_BIGINT && ((MEMORY64 && argType == 'p') || (i53abi && argType == 'j'))) { argConversions += ` ${receiveI64ParamAsI53(name, undefined, false)}\n`; } else { if (argType == 'j' && i53abi) { argConversions += ` ${receiveI64ParamAsI53(name, undefined, false)}\n`; newArgs.push(defineI64Param(name)); } else if (argType == 'p' && CAN_ADDRESS_2GB) { argConversions += ` ${name} >>>= 0;\n`; newArgs.push(name); } else { newArgs.push(name); } } } if (!WASM_BIGINT) { args = newArgs.join(','); } if ((sig[0] == 'j' && i53abi) || (sig[0] == 'p' && MEMORY64)) { // For functions that where we need to mutate the return value, we // also need to wrap the body in an inner function. // If the inner function is marked as `__async` then we need to `await` // the result before casting it to BigInt. Note that we use the `__async` // attribute here rather than the presence of the `async` JS keyword // because this is what tells us that the function is going to return // a promise. i.e. we support async functions that return promises but // are not marked with the `async` keyword (the latter is only necessary // if the function uses the `await` keyword)) const await_ = isAsyncFunction ? 'await ' : ''; const orig_async_ = async_; async_ = isAsyncFunction ? 'async ' : async_; if (oneliner) { // Special case for abort(), this a noreturn function and but closure // compiler doesn't have a way to express that, so it complains if we // do `BigInt(abort(..))`. if (body.startsWith('abort(')) { return snippet; } if (argConversions) { return `${async_}(${args}) => { ${argConversions} return ${makeReturn64(await_ + body)}; }`; } return `${async_}(${args}) => ${makeReturn64(await_ + body)};`; } return `\ ${async_}function(${args}) { ${argConversions} var ret = (${orig_async_}() => { ${body} })(); return ${makeReturn64(await_ + 'ret')}; }`; } // Otherwise no inner function is needed and we covert the arguments // before executing the function body. if (oneliner) { body = `return ${body}`; } return `\ ${async_}function(${args}) { ${argConversions} ${body}; }`; }); } function handleAsyncFunction(snippet, sig) { const return64 = sig && (MEMORY64 && sig.startsWith('p') || sig.startsWith('j')) let handleAsync = 'Asyncify.handleAsync(innerFunc)' if (return64 && ASYNCIFY == 1) { handleAsync = makeReturn64(handleAsync); } return modifyJSFunction(snippet, (args, body, async_, oneliner) => { if (!oneliner) { body = `{\n${body}\n}`; } return `\ function(${args}) { let innerFunc = ${async_} () => ${body}; return ${handleAsync}; }\n`; }); } // The three different inter-thread proxying methods. // See system/lib/pthread/proxying.c const PROXY_ASYNC = 0; const PROXY_SYNC = 1; const PROXY_SYNC_ASYNC = 2; export async function runJSify(outputFile, symbolsOnly) { const libraryItems = []; const symbolDeps = {}; const asyncFuncs = []; let postSets = []; LibraryManager.load(); let outputHandle = process.stdout; if (outputFile) { outputHandle = await fs.open(outputFile, 'w'); } async function writeOutput(str) { await outputHandle.write(str + '\n'); } const symbolsNeeded = DEFAULT_LIBRARY_FUNCS_TO_INCLUDE; symbolsNeeded.push(...extraLibraryFuncs); for (const sym of EXPORTED_RUNTIME_METHODS) { if ('$' + sym in LibraryManager.library) { symbolsNeeded.push('$' + sym); } } for (const key of Object.keys(LibraryManager.library)) { if (!isDecorator(key)) { if (INCLUDE_FULL_LIBRARY || EXPORTED_FUNCTIONS.has(mangleCSymbolName(key))) { symbolsNeeded.push(key); } } } function processLibraryFunction(snippet, symbol, mangled, deps, isStub) { // It is possible that when printing the function as a string on Windows, // the js interpreter we are in returns the string with Windows line endings // \r\n. This is undesirable, since line endings are managed in the form \n // in the output for binary file writes, so make sure the endings are // uniform. snippet = snippet.toString().replace(/\r\n/gm, '\n'); // Is this a shorthand `foo() {}` method syntax? // If so, prepend a function keyword so that it's valid syntax when extracted. if (snippet.startsWith(symbol)) { snippet = 'function ' + snippet; } if (isStub) { return snippet; } // apply LIBRARY_DEBUG if relevant if (LIBRARY_DEBUG && !isJsOnlySymbol(symbol)) { snippet = modifyJSFunction(snippet, (args, body, _async, oneliner) => { var run_func; if (oneliner) { run_func = `var ret = ${body}`; } else { run_func = `var ret = (() => { ${body} })();`; } return `\ function(${args}) { dbg("[library call:${mangled}: " + Array.prototype.slice.call(arguments).map(prettyPrint) + "]"); ${run_func} dbg(" [ return:" + prettyPrint(ret)); return ret; }`; }); } const sig = LibraryManager.library[symbol + '__sig']; const isAsyncFunction = ASYNCIFY && LibraryManager.library[symbol + '__async']; const i53abi = LibraryManager.library[symbol + '__i53abi']; if (i53abi) { if (!sig) { error(`JS library error: '__i53abi' decorator requires '__sig' decorator: '${symbol}'`); } if (!sig.includes('j')) { error(`JS library error: '__i53abi' only makes sense when '__sig' includes 'j' (int64): '${symbol}'`); } } if ( sig && ((i53abi && sig.includes('j')) || ((MEMORY64 || CAN_ADDRESS_2GB) && sig.includes('p'))) ) { snippet = handleI64Signatures(symbol, snippet, sig, i53abi, isAsyncFunction); compileTimeContext.i53ConversionDeps.forEach((d) => deps.push(d)); } if (ASYNCIFY && isAsyncFunction == 'auto') { snippet = handleAsyncFunction(snippet, sig); } const proxyingMode = LibraryManager.library[symbol + '__proxy']; if (proxyingMode) { if (!['sync', 'async', 'none'].includes(proxyingMode)) { error(`JS library error: invalid proxying mode '${symbol}__proxy: ${proxyingMode}' specified`); } if (SHARED_MEMORY && proxyingMode != 'none') { if (PTHREADS) { snippet = modifyJSFunction(snippet, (args, body, async_, oneliner) => { if (oneliner) { body = `return ${body}`; } let proxyMode = PROXY_ASYNC; if (proxyingMode === 'sync') { const isAsyncFunction = LibraryManager.library[symbol + '__async']; if (isAsyncFunction) { proxyMode = PROXY_SYNC_ASYNC; } else { proxyMode = PROXY_SYNC; } } const rtnType = sig?.[0]; const proxyFunc = MEMORY64 && rtnType == 'p' ? 'proxyToMainThreadPtr' : 'proxyToMainThread'; deps.push('$' + proxyFunc); return ` ${async_}function(${args}) { if (ENVIRONMENT_IS_PTHREAD) return ${proxyFunc}(${proxiedFunctionTable.length}, 0, ${proxyMode}${args ? ', ' : ''}${args}); ${body} }\n`; }); } else if (WASM_WORKERS && ASSERTIONS) { // In ASSERTIONS builds add runtime checks that proxied functions are not attempted to be called in Wasm Workers // (since there is no automatic proxying architecture available) snippet = modifyJSFunction( snippet, (args, body) => ` function(${args}) { assert(!ENVIRONMENT_IS_WASM_WORKER, "attempt to call proxied function '${mangled}' from a Wasm Worker (where proxying is not possible)"); ${body} }\n`, ); } proxiedFunctionTable.push(mangled); } } return snippet; } function symbolHandler(symbol) { // In LLVM, exceptions generate a set of functions of form // __cxa_find_matching_catch_1(), __cxa_find_matching_catch_2(), etc. where // the number specifies the number of arguments. In Emscripten, route all // these to a single function 'findMatchingCatch' that takes an array // of argument. if (LINK_AS_CXX && !WASM_EXCEPTIONS && symbol.startsWith('__cxa_find_matching_catch_')) { if (DISABLE_EXCEPTION_THROWING) { error( 'DISABLE_EXCEPTION_THROWING was set (likely due to -fno-exceptions), which means no C++ exception throwing support code is linked in, but exception catching code appears. Either do not set DISABLE_EXCEPTION_THROWING (if you do want exception throwing) or compile all source files with -fno-exceptions (so that no exceptions support code is required); also make sure DISABLE_EXCEPTION_CATCHING is set to the right value - if you want exceptions, it should be off, and vice versa.', ); return; } if (!(symbol in LibraryManager.library)) { // Create a new __cxa_find_matching_catch variant on demand. const num = +symbol.split('_').slice(-1)[0]; compileTimeContext.addCxaCatch(num); } // Continue, with the code below emitting the proper JavaScript based on // what we just added to the library. } function addFromLibrary(symbol, dependent) { // don't process any special identifiers. These are looked up when // processing the base name of the identifier. if (isDecorator(symbol)) { return; } // if the function was implemented in compiled code, there is no need to // include the js version if (WASM_EXPORTS.has(symbol)) { return; } if (symbol in addedLibraryItems) { return; } addedLibraryItems[symbol] = true; const deps = LibraryManager.library[symbol + '__deps'] ??= []; let sig = LibraryManager.library[symbol + '__sig']; if (!WASM_BIGINT && sig && sig[0] == 'j') { // Without WASM_BIGINT functions that return i64 depend on setTempRet0 // to return the upper 32-bits of the result. // See makeReturn64 in parseTools.py. deps.push('setTempRet0'); } const isAsyncFunction = LibraryManager.library[symbol + '__async']; if (ASYNCIFY && isAsyncFunction) { asyncFuncs.push(symbol); } if (symbolsOnly) { if (LibraryManager.library.hasOwnProperty(symbol)) { // Resolve aliases before looking up deps var transitiveDeps = getTransitiveDeps(symbol); symbolDeps[symbol] = transitiveDeps.filter( (d) => !isJsOnlySymbol(d) && !(d in LibraryManager.library), ); } return; } // This gets set to true in the case of dynamic linking for symbols that // are undefined in the main module. In this case we create a stub that // will resolve the correct symbol at runtime, or assert if its missing. let isStub = false; const mangled = mangleCSymbolName(symbol); if (!LibraryManager.library.hasOwnProperty(symbol)) { const isWeakImport = WEAK_IMPORTS.has(symbol); if (!isDefined(symbol) && !isWeakImport) { if (PROXY_TO_PTHREAD && !MAIN_MODULE && symbol == '__main_argc_argv') { error('PROXY_TO_PTHREAD proxies main() for you, but no main exists'); return; } let undefinedSym = symbol; if (symbol === '__main_argc_argv') { undefinedSym = 'main/__main_argc_argv'; } let msg = 'undefined symbol: ' + undefinedSym; if (dependent) msg += ` (referenced by ${dependent})`; if (ERROR_ON_UNDEFINED_SYMBOLS) { error(msg); warnOnce( 'To disable errors for undefined symbols use `-sERROR_ON_UNDEFINED_SYMBOLS=0`', ); warnOnce( mangled + ' may need to be added to EXPORTED_FUNCTIONS if it arrives from a system library', ); } else if (WARN_ON_UNDEFINED_SYMBOLS) { warn(msg); } else { debugLog(msg); } if (symbol === '__main_argc_argv' && STANDALONE_WASM) { warn('To build in STANDALONE_WASM mode without a main(), use emcc --no-entry'); } } // emit a stub that will fail at runtime var stubFunctionBody = `abort('missing function: ${symbol}');` if (MAIN_MODULE) { // Create a stub for this symbol which can later be replaced by the // dynamic linker. If this stub is called before the symbol is // resolved assert in debug builds or trap in release builds. let target = `wasmImports['${symbol}']`; if (ASYNCIFY) { // See the definition of asyncifyStubs in preamble.js for why this // is needed. target = `asyncifyStubs['${symbol}']`; } let assertion = ''; if (ASSERTIONS) { assertion += `if (!${target} || ${target}.stub) abort("external symbol '${symbol}' is missing. perhaps a side module was not linked in? if this function was expected to arrive from a system library, try to build the MAIN_MODULE with EMCC_FORCE_STDLIBS=1 in the environment");\n`; } stubFunctionBody = assertion + `return ${target}(...args);`; } isStub = true; LibraryManager.library[symbol] = new Function('...args', stubFunctionBody); } librarySymbols.push(mangled); const original = LibraryManager.library[symbol]; let snippet = original; const isUserSymbol = LibraryManager.library[symbol + '__user']; // Check for dependencies on `__internal` symbols from user libraries. for (const dep of deps) { if (isUserSymbol && LibraryManager.library[dep + '__internal']) { warn(`user library symbol '${symbol}' depends on internal symbol '${dep}'`); } } let isFunction = typeof snippet == 'function'; let isNativeAlias = false; const postsetId = symbol + '__postset'; const postset = LibraryManager.library[postsetId]; if (postset) { // A postset is either code to run right now, or some text we should emit. // If it's code, it may return some text to emit as well. const postsetString = typeof postset == 'function' ? postset() : postset; if (postsetString && !addedLibraryItems[postsetId]) { addedLibraryItems[postsetId] = true; postSets.push(postsetString + ';'); } } if (LibraryManager.isAlias(snippet)) { // Redirection for aliases. We include the parent, and at runtime // make ourselves equal to it. This avoid having duplicate // functions with identical content. const aliasTarget = snippet; if (WASM_EXPORTS.has(aliasTarget)) { debugLog(`native alias: ${mangled} -> ${aliasTarget}`); nativeAliases[mangled] = aliasTarget; snippet = undefined; isNativeAlias = true; } else { debugLog(`js alias: ${mangled} -> ${aliasTarget}`); snippet = mangleCSymbolName(aliasTarget); // When we have an alias for another JS function we can normally // point them at the same function. However, in some cases (where // signatures are relevant and they differ between and alais and // it's target) we need to construct a forwarding function from // one to the other. const isSigRelevant = MAIN_MODULE || MEMORY64 || CAN_ADDRESS_2GB || sig?.includes('j'); const targetSig = LibraryManager.library[aliasTarget + '__sig']; if (isSigRelevant && sig && targetSig && sig != targetSig) { debugLog(`${symbol}: Alias target (${aliasTarget}) has different signature (${sig} vs ${targetSig})`) isFunction = true; snippet = `(${sigToArgs(sig)}) => ${snippet}(${sigToArgs(targetSig)})`; } } } else if (typeof snippet == 'object') { snippet = stringifyWithFunctions(snippet); addImplicitDeps(snippet, deps); } else if (typeof snippet == 'string' && (snippet.match(/^\s*\([^}]*\)\s*=>/) || snippet.match(/^function\b/))) { // Support functions that are already "stringified" isFunction = true; } if (isFunction) { snippet = processLibraryFunction(snippet, symbol, mangled, deps, isStub); addImplicitDeps(snippet, deps); if (CHECK_DEPS && !isUserSymbol) { checkDependencies(symbol, snippet, deps, postset?.toString()); } } debugLog(`adding ${symbol} (referenced by ${dependent})`); function addDependency(dep) { // dependencies can be JS functions, which we just run if (typeof dep == 'function') { return dep(); } // $noExitRuntime is special since there are conditional usages of it // in libcore.js and libpthread.js. These happen before deps are // processed so depending on it via `__deps` doesn't work. if (dep === '$noExitRuntime') { error( 'noExitRuntime cannot be referenced via __deps mechanism. Use DEFAULT_LIBRARY_FUNCS_TO_INCLUDE or EXPORTED_RUNTIME_METHODS', ); } return addFromLibrary(dep, `${symbol}, referenced by ${dependent}`); } let contentText; if (isFunction) { // Emit the body of a JS library function. if ((USE_ASAN || USE_LSAN) && LibraryManager.library[symbol + '__noleakcheck']) { contentText = modifyJSFunction( snippet, (args, body) => `(${args}) => noLeakCheck(() => {${body}})`, ); deps.push('$noLeakCheck'); } else { contentText = snippet; // Regular JS function that will be executed in the context of the calling thread. } // Give the function the correct (mangled) name. Overwrite it if it's // already named. This must happen after the last call to // modifyJSFunction which could have changed or removed the name. if (contentText.match(/^\s*([^}]*)\s*=>/s)) { // Handle arrow functions contentText = `var ${mangled} = ` + contentText + ';'; } else if (contentText.startsWith('class ')) { // Handle class declarations (which also have typeof == 'function'.) contentText = contentText.replace(/^class(?:\s+(?!extends\b)[^{\s]+)?/, `class ${mangled}`); } else { // Handle regular (non-arrow) functions contentText = contentText.replace(/function(?:\s+([^(]+))?\s*\(/, `function ${mangled}(`); } } else if (typeof snippet == 'string' && snippet.startsWith(';')) { // In JS libraries // foo: ';[code here verbatim]' // emits // 'var foo;[code here verbatim];' contentText = 'var ' + mangled + snippet; if (snippet[snippet.length - 1] != ';' && snippet[snippet.length - 1] != '}') { contentText += ';'; } } else if (typeof snippet == 'undefined') { // For JS library functions that are simply aliases of native symbols, // we don't need to generate anything here. Instead these get included // and exported alongside native symbols. // See `create_receiving` in `tools/emscripten.py`. if (isNativeAlias) { contentText = ''; } else { contentText = `var ${mangled};`; } } else { // In JS libraries // foo: '=[value]' // emits // 'var foo = [value];' if (typeof snippet == 'string' && snippet[0] == '=') { snippet = snippet.slice(1); } contentText = `var ${mangled} = ${snippet};`; } if (contentText && MODULARIZE == 'instance' && (EXPORT_ALL || EXPORTED_FUNCTIONS.has(mangled)) && !isStub) { // In MODULARIZE=instance mode mark JS library symbols are exported at // the point of declaration. contentText = 'export ' + contentText; } // Dynamic linking needs signatures to create proper wrappers. if (sig && MAIN_MODULE) { if (!WASM_BIGINT) { sig = sig[0].replace('j', 'i') + sig.slice(1).replace(/j/g, 'ii'); } contentText += `\n${mangled}.sig = '${sig}';`; } if (ASYNCIFY && isAsyncFunction) { assert(isFunction); contentText += `\n${mangled}.isAsync = true;`; } if (isStub) { contentText += `\n${mangled}.stub = true;`; if (ASYNCIFY && MAIN_MODULE) { contentText += `\nasyncifyStubs['${symbol}'] = undefined;`; } } // Add the docs if they exist and if we are actually emitting a declaration. // See the TODO about wasmTable above. let docs = LibraryManager.library[symbol + '__docs']; let commentText = ''; if (contentText != '' && docs) { commentText += docs + '\n'; } if (EMIT_TSD) { LibraryManager.libraryDefinitions[mangled] = { docs: docs ?? null, snippet: snippet ?? null, }; } const depsText = deps ? deps .map(addDependency) .filter((x) => x != '') .join('\n') + '\n' : ''; return depsText + commentText + contentText; } const JS = addFromLibrary(symbol, 'root reference (e.g. compiled C/C++ code)'); libraryItems.push(JS); } function includeSystemFile(fileName) { writeOutput(getSystemIncludeFile(fileName)); } function includeFile(fileName) { writeOutput(getIncludeFile(fileName)); } function finalCombiner() { const splitPostSets = splitter(postSets, (x) => x.symbol && x.dependencies); postSets = splitPostSets.leftIn; const orderedPostSets = splitPostSets.splitOut; let limit = orderedPostSets.length * orderedPostSets.length; for (let i = 0; i < orderedPostSets.length; i++) { for (let j = i + 1; j < orderedPostSets.length; j++) { if (orderedPostSets[j].symbol in orderedPostSets[i].dependencies) { const temp = orderedPostSets[i]; orderedPostSets[i] = orderedPostSets[j]; orderedPostSets[j] = temp; i--; limit--; assert(limit > 0, 'could not sort postsets'); break; } } } postSets.push(...orderedPostSets); const shellFile = MINIMAL_RUNTIME ? 'shell_minimal.js' : 'shell.js'; includeSystemFile(shellFile); const preFile = MINIMAL_RUNTIME ? 'preamble_minimal.js' : 'preamble.js'; includeSystemFile(preFile); writeOutput('// Begin JS library code\n'); for (const item of libraryItems.concat(postSets)) { writeOutput(indentify(item || '', 2)); } writeOutput('// End JS library code\n'); if (!MINIMAL_RUNTIME) { includeSystemFile('postlibrary.js'); } if (PTHREADS) { writeOutput(` // proxiedFunctionTable specifies the list of functions that can be called // either synchronously or asynchronously from other threads in postMessage()d // or internally queued events. This way a pthread in a Worker can synchronously // access e.g. the DOM on the main thread. var proxiedFunctionTable = [ ${proxiedFunctionTable.join(',\n ')} ]; `); } // This is the main 'post' pass. Print out the generated code // that we have here, together with the rest of the output // that we started to print out earlier (see comment on the // "Final shape that will be created"). writeOutput('// EMSCRIPTEN_END_FUNCS\n'); const postFile = MINIMAL_RUNTIME ? 'postamble_minimal.js' : 'postamble.js'; includeSystemFile(postFile); for (const fileName of POST_JS_FILES) { includeFile(fileName); } if (MODULARIZE && MODULARIZE != 'instance') { includeSystemFile('postamble_modularize.js'); } if (errorOccured()) { throw Error('Aborting compilation due to previous errors'); } writeOutput( '//FORWARDED_DATA:' + JSON.stringify({ librarySymbols, nativeAliases, warnings: warningOccured(), asyncFuncs, libraryDefinitions: LibraryManager.libraryDefinitions, ATPRERUNS: ATPRERUNS.join('\n'), ATMODULES: ATMODULES.join('\n'), ATINITS: ATINITS.join('\n'), ATPOSTCTORS: ATPOSTCTORS.join('\n'), ATMAINS: ATMAINS.join('\n'), ATPOSTRUNS: ATPOSTRUNS.join('\n'), ATEXITS: ATEXITS.join('\n'), }), ); } for (const sym of symbolsNeeded) { symbolHandler(sym); } if (symbolsOnly) { writeOutput( JSON.stringify({ deps: symbolDeps, asyncFuncs, extraLibraryFuncs, }), ); } else { timer.start('finalCombiner') finalCombiner(); timer.stop('finalCombiner') } if (errorOccured()) { throw Error('Aborting compilation due to previous errors'); } if (outputFile) await outputHandle.close(); } addToCompileTimeContext({ extraLibraryFuncs, addedLibraryItems, preJS, });