blob: 28b1123508beebc53eaf49c7847f7abb527e106c [file] [log] [blame] [edit]
/**
* @license
* Copyright 2011 The Emscripten Authors
* SPDX-License-Identifier: MIT
*/
// "use strict";
// Various namespace-like modules
// Constructs an array ['a0', 'a1', 'a2', ..., 'a(n-1)']
function genArgSequence(n) {
const args = [];
for (let i = 0; i < n; ++i) {
args.push('a' + i);
}
return args;
}
// List of functions that were added from the library.
global.libraryFunctions = [];
global.LibraryManager = {
library: {},
structs: {},
loaded: false,
libraries: [],
has: function(name) {
return this.libraries.includes(name);
},
load: function() {
assert(!this.loaded);
this.loaded = true;
// Core system libraries (always linked against)
let libraries = [
'library.js',
'library_int53.js',
'library_formatString.js',
'library_getvalue.js',
'library_math.js',
'library_path.js',
'library_syscall.js',
'library_html5.js',
'library_stack_trace.js',
'library_wasi.js',
'library_dylink.js',
'library_eventloop.js',
];
if (LINK_AS_CXX && !EXCEPTION_HANDLING) {
if (DISABLE_EXCEPTION_THROWING) {
libraries.push('library_exceptions_stub.js');
} else {
libraries.push('library_exceptions.js');
}
}
if (MINIMAL_RUNTIME) {
// Classic runtime brings in string-related functions in the runtime preamble, by including
// runtime_strings_extra.js that contain the same contents as library_strings.js does. In
// MINIMAL_RUNTIME those string functions are available as JS library functions instead from
// library_strings.js, to avoid unconditionally bringing in extra code to the build.
libraries.push('library_strings.js');
} else {
libraries.push('library_browser.js');
libraries.push('library_wget.js');
}
if (EMSCRIPTEN_TRACING) {
libraries.push('library_memoryprofiler.js');
}
if (FILESYSTEM) {
// Core filesystem libraries (always linked against, unless -sFILESYSTEM=0 is specified)
libraries = libraries.concat([
'library_fs.js',
'library_memfs.js',
'library_tty.js',
'library_pipefs.js', // ok to include it by default since it's only used if the syscall is used
'library_sockfs.js', // ok to include it by default since it's only used if the syscall is used
]);
if (NODERAWFS) {
// NODERAWFS requires NODEFS
if (!JS_LIBRARIES.includes('library_nodefs.js')) {
libraries.push('library_nodefs.js');
}
libraries.push('library_noderawfs.js');
}
} else if (WASMFS) {
libraries.push('library_wasmfs.js');
libraries.push('library_wasmfs_js_file.js');
libraries.push('library_wasmfs_jsimpl.js');
libraries.push('library_wasmfs_fetch.js');
libraries.push('library_wasmfs_node.js');
libraries.push('library_wasmfs_opfs.js');
}
// Additional JS libraries (without AUTO_JS_LIBRARIES, link to these explicitly via -lxxx.js)
if (AUTO_JS_LIBRARIES) {
libraries = libraries.concat([
'library_webgl.js',
'library_html5_webgl.js',
'library_openal.js',
'library_sdl.js',
'library_glut.js',
'library_xlib.js',
'library_egl.js',
'library_glfw.js',
'library_uuid.js',
'library_glew.js',
'library_idbstore.js',
'library_async.js',
]);
} else {
if (ASYNCIFY) {
libraries.push('library_async.js');
}
if (USE_SDL == 1) {
libraries.push('library_sdl.js');
}
if (USE_SDL == 2) {
libraries.push('library_egl.js', 'library_webgl.js', 'library_html5_webgl.js');
}
}
if (LZ4) {
libraries.push('library_lz4.js');
}
if (MAX_WEBGL_VERSION >= 2) {
// library_webgl2.js must be included only after library_webgl.js, so if we are
// about to include library_webgl2.js, first squeeze in library_webgl.js.
libraries.push('library_webgl.js');
libraries.push('library_webgl2.js');
}
if (GL_EXPLICIT_UNIFORM_LOCATION || GL_EXPLICIT_UNIFORM_BINDING) {
libraries.push('library_c_preprocessor.js');
}
if (LEGACY_GL_EMULATION) {
libraries.push('library_glemu.js');
}
if (USE_WEBGPU) {
libraries.push('library_webgpu.js');
libraries.push('library_html5_webgpu.js');
}
if (BOOTSTRAPPING_STRUCT_INFO) {
libraries = [
'library_bootstrap.js',
'library_formatString.js',
'library_int53.js',
];
}
if (SUPPORT_BIG_ENDIAN) {
libraries.push('library_little_endian_heap.js');
}
// Add all user specified --js-library files to the link.
// These must be added last after all Emscripten-provided system libraries
// above, so that users can override built-in JS library symbols in their
// own code.
libraries = libraries.concat(JS_LIBRARIES);
// Deduplicate libraries to avoid processing any library file multiple times
libraries = libraries.filter((item, pos) => libraries.indexOf(item) == pos);
// Save the list for has() queries later.
this.libraries = libraries;
for (const filename of libraries) {
const isUserLibrary = nodePath.isAbsolute(filename);
if (VERBOSE) {
if (isUserLibrary) {
printErr('processing user library: ' + filename);
} else {
printErr('processing system library: ' + filename);
}
}
const src = read(filename);
let origLibrary = undefined;
let processed = undefined;
// When we parse user libraries also set `__user` attribute
// on each element so that we can distinguish them later.
if (isUserLibrary) {
origLibrary = this.library;
this.library = new Proxy(this.library, {
set: (target, prop, value) => {
target[prop] = value;
if (!isJsLibraryConfigIdentifier(prop)) {
target[prop + '__user'] = true;
}
return true;
},
});
}
try {
processed = processMacros(preprocess(src, filename));
eval(processed);
} catch (e) {
const details = [e, e.lineNumber ? `line number: ${e.lineNumber}` : ''];
if (VERBOSE) {
details.push((e.stack || '').toString().replace('Object.<anonymous>', filename));
}
if (processed) {
error(`failure to execute js library "${filename}": ${details}`);
if (VERBOSE) {
error(`preprocessed source (you can run a js engine on this to get a clearer error message sometimes):\n=============\n${processed}\n=============`);
} else {
error('use -sVERBOSE to see more details');
}
} else {
error(`failure to process js library "${filename}": ${details}`);
if (VERBOSE) {
error(`original source:\n=============\n${src}\n=============`);
} else {
error('use -sVERBOSE to see more details');
}
}
throw e;
} finally {
if (origLibrary) {
this.library = origLibrary;
}
}
}
// apply synonyms. these are typically not speed-sensitive, and doing it
// this way makes it possible to not include hacks in the compiler
// (and makes it simpler to switch between SDL versions, fastcomp and non-fastcomp, etc.).
const lib = this.library;
libloop: for (const x in lib) {
if (!Object.prototype.hasOwnProperty.call(lib, x)) {
continue;
}
if (isJsLibraryConfigIdentifier(x)) {
const index = x.lastIndexOf('__');
const basename = x.slice(0, index);
if (!(basename in lib)) {
error(`Missing library element '${basename}' for library config '${x}'`);
}
continue;
}
if (typeof lib[x] == 'string') {
let target = x;
while (typeof lib[target] == 'string') {
// ignore code and variable assignments, aliases are just simple names
if (lib[target].search(/[=({; ]/) >= 0) continue libloop;
target = lib[target];
}
if (!isNaN(target)) continue; // This is a number, and so cannot be an alias target.
if (typeof lib[target] == 'undefined' || typeof lib[target] == 'function') {
// When functions are aliased, a signature for the function must be
// provided so that an efficient form of forwarding can be
// implemented.
function testStringType(sig) {
if (typeof lib[sig] != 'undefined' && typeof typeof lib[sig] != 'string') {
error(`${sig} should be a string! (was ${typeof lib[sig]})`);
}
}
const aliasSig = x + '__sig';
const targetSig = target + '__sig';
testStringType(aliasSig);
testStringType(targetSig);
if (typeof lib[aliasSig] == 'string' && typeof lib[targetSig] == 'string' && lib[aliasSig] != lib[targetSig]) {
error(`${aliasSig} (${lib[aliasSig]}) differs from ${targetSig} (${lib[targetSig]})`);
}
const sig = lib[aliasSig] || lib[targetSig];
if (typeof sig != 'string') {
error(`Function ${x} aliases to target function ${target}, but neither the alias or the target provide a signature. Please add a ${targetSig}: 'vifj...' annotation or a ${aliasSig}: 'vifj...' annotation to describe the type of function forwarding that is needed!`);
}
// If only one of the target or the alias specifies a sig then copy
// this signature to the other.
if (!lib[aliasSig]) {
lib[aliasSig] = lib[targetSig];
} else if (!lib[targetSig]) {
lib[targetSig] = lib[aliasSig];
}
if (typeof lib[target] != 'function') {
error(`no alias found for ${x}`);
}
const argCount = sig.length - 1;
if (argCount !== lib[target].length) {
error(`incorrect number of arguments in signature of ${x} (declared: ${argCount}, expected: ${lib[target].length})`);
}
const ret = sig == 'v' ? '' : 'return ';
const args = genArgSequence(argCount).join(',');
lib[x] = new Function(args, `${ret}_${target}(${args});`);
if (!lib[x + '__deps']) lib[x + '__deps'] = [];
lib[x + '__deps'].push(target);
}
}
}
},
};
if (!BOOTSTRAPPING_STRUCT_INFO) {
// Load struct and define information.
const temp = JSON.parse(read(STRUCT_INFO));
C_STRUCTS = temp.structs;
C_DEFINES = temp.defines;
} else {
C_STRUCTS = {};
C_DEFINES = {};
}
// Safe way to access a C define. We check that we don't add library functions with missing defines.
function cDefine(key) {
if (key in C_DEFINES) return C_DEFINES[key];
throw new Error(`Missing C define ${key}! If you just added it to struct_info.json, you need to ./emcc --clear-cache`);
}
function isFSPrefixed(name) {
return name.length > 3 && name[0] === 'F' && name[1] === 'S' && name[2] === '_';
}
// forcing the filesystem exports a few things by default
function isExportedByForceFilesystem(name) {
if (!WASMFS) {
// The old FS has some functionality that WasmFS lacks.
if (name === 'FS_createLazyFile' ||
name === 'FS_createDevice') {
return true;
}
}
return name === 'FS_createPath' ||
name === 'FS_createDataFile' ||
name === 'FS_createPreloadedFile' ||
name === 'FS_unlink' ||
name === 'addRunDependency' ||
name === 'removeRunDependency';
}
// export parts of the JS runtime that the user asked for
function exportRuntime() {
const EXPORTED_RUNTIME_METHODS_SET = new Set(EXPORTED_RUNTIME_METHODS);
// optionally export something.
// in ASSERTIONS mode we show a useful error if it is used without
// being exported. how we show the message depends on whether it's
// a function (almost all of them) or a number.
function maybeExport(name, isNumber) {
// if requested to be exported, export it
if (EXPORTED_RUNTIME_METHODS_SET.has(name)) {
let exported = name;
// the exported name may differ from the internal name
if (isFSPrefixed(exported)) {
// this is a filesystem value, FS.x exported as FS_x
exported = 'FS.' + exported.substr(3);
} else if (exported === 'print') {
exported = 'out';
} else if (exported === 'printErr') {
exported = 'err';
}
return `Module["${name}"] = ${exported};`;
}
// do not export it. but if ASSERTIONS, emit a
// stub with an error, so the user gets a message
// if it is used, that they should export it
if (ASSERTIONS) {
// check if it already exists, to support EXPORT_ALL and other cases
const fssymbol = isExportedByForceFilesystem(name);
if (isNumber) {
return `unexportedRuntimeSymbol('${name}', ${fssymbol});`;
} else {
return `unexportedRuntimeFunction('${name}', ${fssymbol});`;
}
}
return '';
}
function maybeExportNumber(name) {
return maybeExport(name, true);
}
// All possible runtime elements that can be exported
let runtimeElements = [
'ccall',
'cwrap',
'allocate',
'UTF8ArrayToString',
'UTF8ToString',
'stringToUTF8Array',
'stringToUTF8',
'lengthBytesUTF8',
'addOnPreRun',
'addOnInit',
'addOnPreMain',
'addOnExit',
'addOnPostRun',
'addRunDependency',
'removeRunDependency',
'FS_createFolder',
'FS_createPath',
'FS_createDataFile',
'FS_createPreloadedFile',
'FS_createLazyFile',
'FS_createLink',
'FS_createDevice',
'FS_unlink',
'getLEB',
'getFunctionTables',
'alignFunctionTables',
'registerFunctions',
'addFunction',
'removeFunction',
'prettyPrint',
'getCompilerSetting',
'print',
'printErr',
'getTempRet0',
'setTempRet0',
'callMain',
'abort',
'keepRuntimeAlive',
'wasmMemory',
];
if (USE_PTHREADS && ALLOW_MEMORY_GROWTH) {
runtimeElements = runtimeElements.concat([
'GROWABLE_HEAP_I8',
'GROWABLE_HEAP_U8',
'GROWABLE_HEAP_I16',
'GROWABLE_HEAP_U16',
'GROWABLE_HEAP_I32',
'GROWABLE_HEAP_U32',
'GROWABLE_HEAP_F32',
'GROWABLE_HEAP_F64',
]);
}
if (USE_OFFSET_CONVERTER) {
runtimeElements.push('WasmOffsetConverter');
}
if (LOAD_SOURCE_MAP) {
runtimeElements.push('WasmSourceMap');
}
if (!MINIMAL_RUNTIME) {
// MINIMAL_RUNTIME has moved these functions to library_strings.js
runtimeElements = runtimeElements.concat([
'warnOnce',
'stackSave',
'stackRestore',
'stackAlloc',
'AsciiToString',
'stringToAscii',
'UTF16ToString',
'stringToUTF16',
'lengthBytesUTF16',
'UTF32ToString',
'stringToUTF32',
'lengthBytesUTF32',
'allocateUTF8',
'allocateUTF8OnStack',
'ExitStatus',
'intArrayFromString',
'intArrayToString',
'writeStringToMemory',
'writeArrayToMemory',
'writeAsciiToMemory',
]);
}
if (STACK_OVERFLOW_CHECK) {
runtimeElements.push('writeStackCookie');
runtimeElements.push('checkStackCookie');
}
if (SUPPORT_BASE64_EMBEDDING) {
runtimeElements.push('intArrayFromBase64');
runtimeElements.push('tryParseAsDataURI');
}
// dynCall_* methods are not hardcoded here, as they
// depend on the file being compiled. check for them
// and add them.
for (const name of EXPORTED_RUNTIME_METHODS_SET) {
if (/^dynCall_/.test(name)) {
// a specific dynCall; add to the list
runtimeElements.push(name);
}
}
// Add JS library elements such as FS, GL, ENV, etc. These are prefixed with
// '$ which indicates they are JS methods.
const runtimeElementsSet = new Set(runtimeElements);
for (const ident in LibraryManager.library) {
if (ident[0] === '$' && !isJsLibraryConfigIdentifier(ident) && !LibraryManager.library[ident + '__internal']) {
const jsname = ident.substr(1);
assert(!runtimeElementsSet.has(jsname), 'runtimeElements contains library symbol: ' + ident);
runtimeElements.push(jsname);
}
}
const runtimeNumbers = [
'ALLOC_NORMAL',
'ALLOC_STACK',
];
if (ASSERTIONS) {
// check all exported things exist, warn about typos
const runtimeElementsSet = new Set(runtimeElements);
const runtimeNumbersSet = new Set(runtimeNumbers);
for (const name of EXPORTED_RUNTIME_METHODS_SET) {
if (!runtimeElementsSet.has(name) && !runtimeNumbersSet.has(name)) {
printErr(`warning: invalid item (maybe a typo?) in EXPORTED_RUNTIME_METHODS: ${name}`);
}
}
}
let exports = runtimeElements.map((name) => maybeExport(name));
exports = exports.concat(runtimeNumbers.map((name) => maybeExportNumber(name)));
exports = exports.filter((name) => name != '');
return exports.join('\n');
}