blob: 383ca2000cc1e221580898f453e50078c9482f2d [file] [log] [blame] [edit]
/**
* @license
* Copyright 2010 The Emscripten Authors
* SPDX-License-Identifier: MIT
*/
// "use strict";
// Convert analyzed data to javascript. Everything has already been calculated
// before this stage, which just does the final conversion to JavaScript.
global.addedLibraryItems = {};
// 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).
global.proxiedFunctionTable = ['null'/* Reserve index 0 for an undefined function*/];
// 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.substr(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: leftIn, splitOut: splitOut};
}
// Functions that start with '$' should not be exported to the wasm module.
// They are intended to be exclusive to JS code only.
function isJsOnlyIdentifier(ident) {
return ident[0] == '$';
}
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 + "'";
}
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(',') + ']';
}
return '{' + Object.keys(obj).map((key) => escapeJSONKey(key) + ':' + stringifyWithFunctions(obj[key])).join(',') + '}';
}
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 libary_dylink.py so can
// always be considered as defined.
if (RELOCATABLE && symName.startsWith('invoke_')) {
return true;
}
return false;
}
// JSifier
function runJSify(functionsOnly) {
const mainPass = !functionsOnly;
const functionStubs = [];
const itemsDict = {type: [], functionStub: [], function: [], globalVariablePostSet: []};
if (mainPass) {
// Add additional necessary items for the main pass. We can now do this since types are parsed (types can be used through
// generateStructInfo in library.js)
LibraryManager.load();
const libFuncsToInclude = DEFAULT_LIBRARY_FUNCS_TO_INCLUDE;
for (const sym of EXPORTED_RUNTIME_METHODS) {
if ('$' + sym in LibraryManager.library) {
libFuncsToInclude.push('$' + sym);
}
}
if (INCLUDE_FULL_LIBRARY) {
for (const key in LibraryManager.library) {
if (!isJsLibraryConfigIdentifier(key)) {
libFuncsToInclude.push(key);
}
}
}
libFuncsToInclude.forEach((ident) => {
functionStubs.push({
identOrig: ident,
identMangled: mangleCSymbolName(ident),
});
});
}
function convertPointerParams(snippet, sig) {
// Automatically convert any incoming pointer arguments from BigInt
// to double (this limits the range to int53).
// And convert the return value if the function returns a pointer.
return modifyFunction(snippet, (name, args, body) => {
let argLines = args.split('\n');
argLines = argLines.map((line) => line.split('//')[0]);
const argNames = argLines.join(' ').split(',').map((name) => name.trim());
let newArgs = [];
let argConvertions = '';
for (let i = 1; i < sig.length; i++) {
const name = argNames[i - 1];
if (sig[i] == 'p') {
argConvertions += ` ${name} = Number(${name});\n`;
newArgs.push(`Number(${name})`);
} else {
newArgs.push(name);
}
}
if (sig[0] == 'p') {
// For functions that return a pointer we need to convert
// the return value too, which means we need to wrap the
// body in an inner function.
newArgs = newArgs.join(',');
return `\
function ${name}(${args}) {
var ret = ((${args}) => { ${body} })(${newArgs});
return BigInt(ret);
}`;
}
// Otherwise no inner function is needed and we covert the arguments
// before executing the function body.
return `\
function ${name}(${args}) {
${argConvertions}
${body};
}`;
});
}
function processLibraryFunction(snippet, ident, finalName, deps) {
// 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');
// name the function; overwrite if it's already named
snippet = snippet.replace(/function(?:\s+([^(]+))?\s*\(/, 'function ' + finalName + '(');
// apply LIBRARY_DEBUG if relevant
if (LIBRARY_DEBUG && !isJsOnlyIdentifier(ident)) {
snippet = modifyFunction(snippet, (name, args, body) => `\
function ${name}(${args}) {
var ret = (function() { if (runtimeDebug) err("[library call:${finalName}: " + Array.prototype.slice.call(arguments).map(prettyPrint) + "]");
${body}
}).apply(this, arguments);
if (runtimeDebug && typeof ret != "undefined") err(" [ return:" + prettyPrint(ret));
return ret;
}`);
}
if (MEMORY64) {
const sig = LibraryManager.library[ident + '__sig'];
if (sig && sig.includes('p')) {
snippet = convertPointerParams(snippet, sig);
}
}
return snippet;
}
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 automatically generated by
// the {{{ makeDynCall }}}, and {{{ runtimeKeepalivePush/Pop }}} macros.
const autoDeps = [
'getDynCaller',
'getWasmTableEntry',
'runtimeKeepalivePush',
'runtimeKeepalivePop',
];
for (const dep of autoDeps) {
if (snippet.includes(dep + '(')) {
deps.push('$' + dep);
}
}
}
// functionStub
function functionStubHandler(item) {
// 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 '__cxa_find_matching_catch' that variadically
// processes all of these functions using JS 'arguments' object.
if (item.identMangled.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-except (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;
}
const num = +item.identMangled.split('_').slice(-1)[0];
addCxaCatch(num);
// Continue, with the code below emitting the proper JavaScript based on
// what we just added to the library.
}
const TOP_LEVEL = 'top-level compiled C/C++ code';
function addFromLibrary(item, dependent) {
// dependencies can be JS functions, which we just run
if (typeof item == 'function') return item();
const ident = item.identOrig;
const finalName = item.identMangled;
if (ident in addedLibraryItems) return '';
addedLibraryItems[ident] = true;
// don't process any special identifiers. These are looked up when processing the base name of the identifier.
if (isJsLibraryConfigIdentifier(ident)) {
return '';
}
// if the function was implemented in compiled code, there is no need to include the js version
if (WASM_EXPORTS.has(ident)) {
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;
if (!LibraryManager.library.hasOwnProperty(ident)) {
if (ONLY_CALC_JS_SYMBOLS) {
return;
}
const isWeakImport = WEAK_IMPORTS.has(ident);
if (!isDefined(ident) && !isWeakImport) {
if (PROXY_TO_PTHREAD && !MAIN_MODULE && ident == '__main_argc_argv') {
error('PROXY_TO_PTHREAD proxies main() for you, but no main exists');
return;
}
let undefinedSym = ident;
if (ident === '__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);
if (dependent == TOP_LEVEL && !LLD_REPORT_UNDEFINED) {
warnOnce('Link with `-sLLD_REPORT_UNDEFINED` to get more information on undefined symbols');
}
warnOnce('To disable errors for undefined symbols use `-sERROR_ON_UNDEFINED_SYMBOLS=0`');
warnOnce(finalName + ' may need to be added to EXPORTED_FUNCTIONS if it arrives from a system library');
} else if (VERBOSE || WARN_ON_UNDEFINED_SYMBOLS) {
warn(msg);
}
if (ident === '__main_argc_argv' && STANDALONE_WASM) {
warn('To build in STANDALONE_WASM mode without a main(), use emcc --no-entry');
}
}
if (!RELOCATABLE) {
// emit a stub that will fail at runtime
LibraryManager.library[ident] = new Function(`err('missing function: ${ident}'); abort(-1);`);
// We have already warned/errored about this function, so for the purposes of Closure use, mute all type checks
// regarding this function, marking ot a variadic function that can take in anything and return anything.
// (not useful to warn/error multiple times)
LibraryManager.library[ident + '__docs'] = '/** @type {function(...*):?} */';
} else {
const target = `Module['${finalName}']`;
let assertion = '';
if (ASSERTIONS) {
assertion += `if (!${target}) abort("external symbol '${ident}' 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`;
}
const functionBody = assertion + `return ${target}.apply(null, arguments);`;
LibraryManager.library[ident] = new Function(functionBody);
isStub = true;
}
}
const original = LibraryManager.library[ident];
let snippet = original;
const deps = LibraryManager.library[ident + '__deps'] || [];
if (!Array.isArray(deps)) {
error(`JS library directive ${ident}__deps=${deps.toString()} is of type ${typeof deps}, but it should be an array!`);
return;
}
const isUserSymbol = LibraryManager.library[ident + '__user'];
deps.forEach((dep) => {
if (typeof snippet == 'string' && !(dep in LibraryManager.library)) {
warn(`missing library dependency ${dep}, make sure you are compiling with the right options (see #if in src/library*.js)`);
}
if (isUserSymbol && LibraryManager.library[dep + '__internal']) {
warn(`user library symbol '${ident}' depends on internal symbol '${dep}'`);
}
});
let isFunction = false;
if (typeof snippet == 'string') {
if (snippet[0] != '=') {
const target = LibraryManager.library[snippet];
if (target) {
// Redirection for aliases. We include the parent, and at runtime make ourselves equal to it.
// This avoid having duplicate functions with identical content.
const redirectedTarget = snippet;
deps.push(redirectedTarget);
snippet = mangleCSymbolName(redirectedTarget);
}
}
} else if (typeof snippet == 'object') {
snippet = stringifyWithFunctions(snippet);
addImplicitDeps(snippet, deps);
} else if (typeof snippet == 'function') {
isFunction = true;
snippet = processLibraryFunction(snippet, ident, finalName, deps);
addImplicitDeps(snippet, deps);
}
librarySymbols.push(finalName);
if (ONLY_CALC_JS_SYMBOLS) {
return '';
}
const postsetId = ident + '__postset';
let 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.
if (typeof postset == 'function') {
postset = postset();
}
if (postset && !addedLibraryItems[postsetId]) {
addedLibraryItems[postsetId] = true;
itemsDict.globalVariablePostSet.push({
JS: postset + ';',
});
}
}
if (VERBOSE) {
printErr(`adding ${finalName} and deps ${deps} : ` + (snippet + '').substr(0, 40));
}
const identDependents = ident + "__deps: ['" + deps.join("','") + "']";
function addDependency(dep) {
if (typeof dep != 'function') {
dep = {identOrig: dep, identMangled: mangleCSymbolName(dep)};
}
return addFromLibrary(dep, `${identDependents}, referenced by ${dependent}`);
}
let contentText;
if (isFunction) {
// Emit the body of a JS library function.
const proxyingMode = LibraryManager.library[ident + '__proxy'];
if (SHARED_MEMORY && proxyingMode) {
if (proxyingMode !== 'sync' && proxyingMode !== 'async') {
throw new Error(`Invalid proxyingMode ${ident}__proxy: '${proxyingMode}' specified!`);
}
const sync = proxyingMode === 'sync';
assert(typeof original == 'function');
if (USE_PTHREADS) {
contentText = modifyFunction(snippet, (name, args, body) => `
function ${name}(${args}) {
if (ENVIRONMENT_IS_PTHREAD)
return _emscripten_proxy_to_main_thread_js(${proxiedFunctionTable.length}, ${+sync}${args ? ', ' : ''}${args});
${body}
}\n`);
} else if (WASM_WORKERS) {
if (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)
contentText = modifyFunction(snippet, (name, args, body) => `
function ${name}(${args}) {
assert(!ENVIRONMENT_IS_WASM_WORKER, "Attempted to call proxied function '${name}' in a Wasm Worker, but in Wasm Worker enabled builds, proxied function architecture is not available!");
${body}
}\n`);
} else {
// In non-ASSERTIONS builds directly emit the original function.
contentText = snippet;
}
}
proxiedFunctionTable.push(finalName);
} else if ((USE_ASAN || USE_LSAN || UBSAN_RUNTIME) && LibraryManager.library[ident + '__noleakcheck']) {
contentText = modifyFunction(snippet, (name, args, body) => `
function ${name}(${args}) {
return withBuiltinMalloc(function() {
${body}
});
}\n`);
deps.push('$withBuiltinMalloc');
} else {
contentText = snippet; // Regular JS function that will be executed in the context of the calling thread.
}
} else if (typeof snippet == 'string' && snippet.startsWith(';')) {
// In JS libraries
// foo: ';[code here verbatim]'
// emits
// 'var foo;[code here verbatim];'
contentText = 'var ' + finalName + snippet;
if (snippet[snippet.length - 1] != ';' && snippet[snippet.length - 1] != '}') contentText += ';';
} else {
// In JS libraries
// foo: '=[value]'
// emits
// 'var foo = [value];'
if (typeof snippet == 'string' && snippet[0] == '=') {
snippet = snippet.substr(1);
}
contentText = `var ${finalName} = ${snippet};`;
}
const sig = LibraryManager.library[ident + '__sig'];
// asm module exports are done in emscripten.py, after the asm module is ready. Here
// we also export library methods as necessary.
if ((EXPORT_ALL || EXPORTED_FUNCTIONS.has(finalName)) && !isStub) {
contentText += `\nModule["${finalName}"] = ${finalName};`;
}
// Relocatable code needs signatures to create proper wrappers. Stack
// switching needs signatures so we can create a proper
// WebAssembly.Function with the signature for the Promise API.
// TODO: For asyncify we could only add the signatures we actually need,
// of async imports/exports.
if (sig && (RELOCATABLE || ASYNCIFY == 2)) {
contentText += `\n${finalName}.sig = '${sig}';`;
}
if (isStub) {
contentText += `\n${finalName}.stub = true;`;
}
let commentText = '';
if (LibraryManager.library[ident + '__docs']) {
commentText = LibraryManager.library[ident + '__docs'] + '\n';
}
const depsText = (deps ? deps.map(addDependency).filter((x) => x != '').join('\n') + '\n' : '');
return depsText + commentText + contentText;
}
itemsDict.functionStub.push(item);
item.JS = addFromLibrary(item, TOP_LEVEL);
}
// Final combiner
function finalCombiner() {
const splitPostSets = splitter(itemsDict.globalVariablePostSet, (x) => x.ident && x.dependencies);
itemsDict.globalVariablePostSet = 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].ident 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;
}
}
}
itemsDict.globalVariablePostSet = itemsDict.globalVariablePostSet.concat(orderedPostSets);
//
if (!mainPass) {
const generated = itemsDict.function.concat(itemsDict.type);
print(generated.map((item) => item.JS).join('\n'));
return;
}
const shellFile = MINIMAL_RUNTIME ? 'shell_minimal.js' : 'shell.js';
const shellParts = read(shellFile).split('{{BODY}}');
print(processMacros(preprocess(shellParts[0], shellFile)));
let pre;
if (MINIMAL_RUNTIME) {
pre = processMacros(preprocess(read('preamble_minimal.js'), 'preamble_minimal.js'));
} else {
pre = processMacros(preprocess(read('support.js'), 'support.js')) +
processMacros(preprocess(read('preamble.js'), 'preamble.js'));
}
print(pre);
// Print out global variables and postsets TODO: batching
runJSify(true);
const generated = itemsDict.functionStub.concat(itemsDict.globalVariablePostSet);
generated.forEach((item) => print(indentify(item.JS || '', 2)));
if (USE_PTHREADS) {
print('\n // 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.');
print('\nvar proxiedFunctionTable = [' + proxiedFunctionTable.join() + '];\n');
}
if (!MINIMAL_RUNTIME) {
print('var ASSERTIONS = ' + !!ASSERTIONS + ';\n');
}
if ((SUPPORT_BASE64_EMBEDDING || FORCE_FILESYSTEM) && !MINIMAL_RUNTIME) {
print(preprocess(read('base64Utils.js')));
}
if (abortExecution) throw Error('Aborting compilation due to previous errors');
// 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").
print('// EMSCRIPTEN_END_FUNCS\n');
if (HEADLESS) {
print('if (!ENVIRONMENT_IS_WEB) {');
print(read('headlessCanvas.js'));
print('\n');
print(read('headless.js').replace("'%s'", "'http://emscripten.org'").replace("'?%s'", "''").replace("'?%s'", "'/'").replace('%s,', 'null,').replace('%d', '0'));
print('}');
}
if (PROXY_TO_WORKER) {
print('if (ENVIRONMENT_IS_WORKER) {\n');
print(read('webGLWorker.js'));
print(processMacros(preprocess(read('proxyWorker.js'), 'proxyWorker.js')));
print('}');
}
if (DETERMINISTIC) {
print(read('deterministic.js'));
}
const postFile = MINIMAL_RUNTIME ? 'postamble_minimal.js' : 'postamble.js';
const post = processMacros(preprocess(read(postFile), postFile));
print(post);
print(processMacros(preprocess(shellParts[1], shellFile)));
print('\n//FORWARDED_DATA:' + JSON.stringify({
librarySymbols: librarySymbols,
warnings: warnings,
ATINITS: ATINITS.join('\n'),
ATMAINS: ATMAINS.join('\n'),
ATEXITS: ATEXITS.join('\n'),
}));
}
// Data
if (mainPass) {
functionStubs.forEach(functionStubHandler);
}
finalCombiner();
}