| /** |
| * @license |
| * Copyright 2011 The Emscripten Authors |
| * SPDX-License-Identifier: MIT |
| */ |
| |
| import * as path from 'node:path'; |
| import {fileURLToPath} from 'node:url'; |
| import assert from 'node:assert'; |
| |
| import { |
| isDecorator, |
| isJsOnlySymbol, |
| error, |
| readFile, |
| pushCurrentFile, |
| popCurrentFile, |
| printErr, |
| addToCompileTimeContext, |
| runInMacroContext, |
| mergeInto, |
| localFile, |
| } from './utility.mjs'; |
| import {preprocess, processMacros} from './parseTools.mjs'; |
| |
| // Various namespace-like modules |
| |
| // List of symbols that were added from the library. |
| export const librarySymbols = []; |
| |
| const srcDir = fileURLToPath(new URL('.', import.meta.url)); |
| const systemLibdir = path.join(srcDir, 'lib'); |
| |
| function isBeneath(childPath, parentPath) { |
| const relativePath = path.relative(parentPath, childPath); |
| return !relativePath.startsWith('..') && !path.isAbsolute(relativePath); |
| } |
| |
| function calculateLibraries() { |
| // Core system libraries (always linked against) |
| let libraries = [ |
| 'libint53.js', |
| 'libcore.js', |
| 'libsigs.js', |
| 'libccall.js', |
| 'libaddfunction.js', |
| 'libformatString.js', |
| 'libgetvalue.js', |
| 'libmath.js', |
| 'libpath.js', |
| 'libstrings.js', |
| 'libhtml5.js', |
| 'libstack_trace.js', |
| 'libwasi.js', |
| 'libeventloop.js', |
| 'libpromise.js', |
| ]; |
| |
| if (LINK_AS_CXX) { |
| if (DISABLE_EXCEPTION_THROWING && !WASM_EXCEPTIONS) { |
| libraries.push('libexceptions_stub.js'); |
| } else { |
| libraries.push('libexceptions.js'); |
| } |
| } |
| |
| if (!MINIMAL_RUNTIME) { |
| libraries.push('libbrowser.js'); |
| libraries.push('libwget.js'); |
| } |
| |
| if (!STANDALONE_WASM) { |
| libraries.push('libtime.js'); |
| } |
| |
| if (EMSCRIPTEN_TRACING) { |
| libraries.push('libmemoryprofiler.js'); |
| } |
| |
| if (SUPPORT_BASE64_EMBEDDING || ENVIRONMENT_MAY_BE_SHELL) { |
| libraries.push('libbase64.js'); |
| } |
| |
| if (AUTODEBUG) { |
| libraries.push('libautodebug.js'); |
| } |
| |
| if (!WASMFS) { |
| libraries.push('libsyscall.js'); |
| } |
| |
| if (RELOCATABLE) { |
| libraries.push('libdylink.js'); |
| } |
| |
| if (FILESYSTEM) { |
| libraries.push('libfs_shared.js'); |
| if (WASMFS) { |
| libraries.push( |
| 'libwasmfs.js', |
| 'libwasmfs_js_file.js', |
| 'libwasmfs_jsimpl.js', |
| 'libwasmfs_fetch.js', |
| 'libwasmfs_node.js', |
| 'libwasmfs_opfs.js', |
| ); |
| } else { |
| // Core filesystem libraries (always linked against, unless -sFILESYSTEM=0 is specified) |
| libraries.push( |
| 'libfs.js', |
| 'libmemfs.js', |
| 'libtty.js', |
| 'libpipefs.js', // ok to include it by default since it's only used if the syscall is used |
| 'libsockfs.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('libnodefs.js')) { |
| libraries.push('libnodefs.js'); |
| } |
| libraries.push('libnoderawfs.js'); |
| // NODERAWFS overwrites libpath.js |
| libraries.push('libnodepath.js'); |
| } |
| } |
| } |
| |
| // Additional JS libraries (without AUTO_JS_LIBRARIES, link to these explicitly via -lxxx.js) |
| if (AUTO_JS_LIBRARIES) { |
| libraries.push( |
| 'libwebgl.js', |
| 'libhtml5_webgl.js', |
| 'libopenal.js', |
| 'libglut.js', |
| 'libxlib.js', |
| 'libegl.js', |
| 'libuuid.js', |
| 'libglew.js', |
| 'libidbstore.js', |
| 'libasync.js', |
| ); |
| if (USE_SDL != 2) { |
| libraries.push('libsdl.js'); |
| } |
| } else { |
| if (ASYNCIFY) { |
| libraries.push('libasync.js'); |
| } |
| if (USE_SDL == 1) { |
| libraries.push('libsdl.js'); |
| } |
| if (USE_SDL == 2) { |
| libraries.push('libegl.js', 'libwebgl.js', 'libhtml5_webgl.js'); |
| } |
| } |
| |
| if (USE_GLFW) { |
| libraries.push('libglfw.js'); |
| } |
| |
| if (LZ4) { |
| libraries.push('liblz4.js'); |
| } |
| |
| if (SHARED_MEMORY) { |
| libraries.push('libatomic.js'); |
| } |
| |
| if (MAX_WEBGL_VERSION >= 2) { |
| // libwebgl2.js must be included only after libwebgl.js, so if we are |
| // about to include libwebgl2.js, first squeeze in libwebgl.js. |
| libraries.push('libwebgl.js'); |
| libraries.push('libwebgl2.js'); |
| } |
| |
| if (GL_EXPLICIT_UNIFORM_LOCATION || GL_EXPLICIT_UNIFORM_BINDING) { |
| libraries.push('libc_preprocessor.js'); |
| } |
| |
| if (LEGACY_GL_EMULATION) { |
| libraries.push('libglemu.js'); |
| } |
| |
| if (USE_WEBGPU) { |
| libraries.push('libwebgpu.js'); |
| libraries.push('libhtml5_webgpu.js'); |
| } |
| |
| if (!STRICT) { |
| libraries.push('liblegacy.js'); |
| } |
| |
| if (BOOTSTRAPPING_STRUCT_INFO) { |
| libraries = ['libbootstrap.js', 'libformatString.js', 'libstrings.js', 'libint53.js']; |
| } |
| |
| if (SUPPORT_BIG_ENDIAN) { |
| libraries.push('liblittle_endian_heap.js'); |
| } |
| |
| // Resolve system libraries |
| libraries = libraries.map((filename) => path.join(systemLibdir, filename)); |
| |
| // 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.push(...JS_LIBRARIES); |
| |
| // Deduplicate libraries to avoid processing any library file multiple times |
| libraries = libraries.filter((item, pos) => libraries.indexOf(item) == pos); |
| |
| return libraries; |
| } |
| |
| export const LibraryManager = { |
| library: {}, |
| // The JS and JS docs of each library definition indexed my mangled name. |
| libraryDefinitions: {}, |
| structs: {}, |
| loaded: false, |
| libraries: [], |
| |
| has(name) { |
| if (!path.isAbsolute(name)) { |
| // Our libraries used to be called `library_xxx.js` rather than |
| // `lib_xx.js`. In case we have external code using this function |
| // we check for the old form too. |
| if (name.startsWith('library_')) { |
| name = name.replace('library_', 'lib'); |
| } |
| name = path.join(systemLibdir, name); |
| } |
| return this.libraries.includes(name); |
| }, |
| |
| load() { |
| assert(!this.loaded); |
| this.loaded = true; |
| // Save the list for has() queries later. |
| this.libraries = calculateLibraries(); |
| |
| const userLibraryProxy = new Proxy(this.library, { |
| set(target, prop, value) { |
| target[prop] = value; |
| if (!isDecorator(prop)) { |
| target[prop + '__user'] = true; |
| } |
| return true; |
| }, |
| }); |
| |
| for (let filename of this.libraries) { |
| const isUserLibrary = !isBeneath(filename, systemLibdir); |
| |
| if (VERBOSE) { |
| if (isUserLibrary) { |
| printErr('processing user library: ' + filename); |
| } else { |
| printErr('processing system library: ' + 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 = userLibraryProxy; |
| } |
| pushCurrentFile(filename); |
| try { |
| processed = processMacros(preprocess(filename), filename); |
| runInMacroContext(processed, {filename: filename.replace(/\.\w+$/, '.preprocessed$&')}); |
| } catch (e) { |
| error(`failure to execute js library "${filename}":`); |
| if (VERBOSE) { |
| const orig = readFile(filename); |
| if (processed) { |
| error( |
| `preprocessed source (you can run a js engine on this to get a clearer error message sometimes):\n=============\n${processed}\n=============`, |
| ); |
| } else { |
| error(`original source:\n=============\n${orig}\n=============`); |
| } |
| } else { |
| error('use -sVERBOSE to see more details'); |
| } |
| throw e; |
| } finally { |
| popCurrentFile(); |
| if (origLibrary) { |
| this.library = origLibrary; |
| } |
| } |
| } |
| }, |
| }; |
| |
| // options is optional input object containing mergeInto params |
| // currently, it can contain |
| // |
| // key: noOverride, value: true |
| // if it is set, it prevents symbol redefinition and shows error |
| // in case of redefinition |
| // |
| // key: checkSig, value: true |
| // if it is set, __sig is checked for functions and error is reported |
| // if <function name>__sig is missing |
| function addToLibrary(obj, options = null) { |
| mergeInto(LibraryManager.library, obj, options); |
| } |
| |
| let structs = {}; |
| let defines = {}; |
| |
| /** |
| * Read JSON file containing struct and macro/define information |
| * that can then be used in JavaScript via macros. |
| */ |
| function loadStructInfo(filename) { |
| const temp = JSON.parse(readFile(filename)); |
| Object.assign(structs, temp.structs); |
| Object.assign(defines, temp.defines); |
| } |
| |
| if (!BOOTSTRAPPING_STRUCT_INFO) { |
| // Load struct and define information. |
| if (MEMORY64) { |
| loadStructInfo(localFile('struct_info_generated_wasm64.json')); |
| } else { |
| loadStructInfo(localFile('struct_info_generated.json')); |
| } |
| } |
| |
| // Use proxy objects for C_DEFINES and C_STRUCTS so that we can give useful |
| // error messages. |
| const C_STRUCTS = new Proxy(structs, { |
| get(target, prop) { |
| if (!(prop in target)) { |
| throw new Error( |
| `Missing C struct ${prop}! If you just added it to struct_info.json, you need to run ./tools/gen_struct_info.py (then run a second time with --wasm64)`, |
| ); |
| } |
| return target[prop]; |
| }, |
| }); |
| |
| const C_DEFINES = new Proxy(defines, { |
| get(target, prop) { |
| if (!(prop in target)) { |
| throw new Error( |
| `Missing C define ${prop}! If you just added it to struct_info.json, you need to run ./tools/gen_struct_info.py (then run a second time with --wasm64)`, |
| ); |
| } |
| return target[prop]; |
| }, |
| }); |
| |
| // shorter alias for C_DEFINES |
| const cDefs = C_DEFINES; |
| |
| // Legacy function that existed solely to give error message. These are now |
| // provided by the cDefs proxy object above. |
| function cDefine(key) { |
| return cDefs[key]; |
| } |
| |
| function isInternalSymbol(ident) { |
| return ident + '__internal' in LibraryManager.library; |
| } |
| |
| function getUnusedLibrarySymbols() { |
| const librarySymbolSet = new Set(librarySymbols); |
| const missingSyms = new Set(); |
| for (const [ident, value] of Object.entries(LibraryManager.library)) { |
| if (typeof value === 'function' || typeof value === 'number') { |
| if (isJsOnlySymbol(ident) && !isDecorator(ident) && !isInternalSymbol(ident)) { |
| const name = ident.slice(1); |
| if (!librarySymbolSet.has(name)) { |
| missingSyms.add(name); |
| } |
| } |
| } |
| } |
| return missingSyms; |
| } |
| |
| // When running with ASSERTIONS enabled we create stubs for each library |
| // function that that was not included in the build. This gives useful errors |
| // when library dependencies are missing from `__deps` or depended on without |
| // being added to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE |
| // TODO(sbc): These errors could potentially be generated at build time via |
| // some kind of acorn pass that searched for uses of these missing symbols. |
| function addMissingLibraryStubs(unusedLibSymbols) { |
| let rtn = ''; |
| rtn += 'var missingLibrarySymbols = [\n'; |
| for (const sym of unusedLibSymbols) { |
| rtn += ` '${sym}',\n`; |
| } |
| rtn += '];\n'; |
| rtn += 'missingLibrarySymbols.forEach(missingLibrarySymbol)\n'; |
| return rtn; |
| } |
| |
| function exportSymbol(name) { |
| // In MODULARIZE=instance mode symbols are exported by being included in |
| // an export { foo, bar } list so we build up the simple list of names |
| if (MODULARIZE === 'instance') { |
| return name; |
| } |
| return `Module['${name}'] = ${name};`; |
| } |
| |
| // export parts of the JS runtime that the user asked for |
| function exportRuntimeSymbols() { |
| // optionally export something. |
| function shouldExport(name) { |
| // If requested to be exported, export it. |
| if (EXPORTED_RUNTIME_METHODS.has(name)) { |
| // Unless we are in MODULARIZE=instance mode then HEAP objects are |
| // exported separately in updateMemoryViews |
| if (MODULARIZE == 'instance' || !name.startsWith('HEAP')) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // All possible runtime elements that can be exported |
| let runtimeElements = [ |
| 'run', |
| 'addRunDependency', |
| 'removeRunDependency', |
| 'out', |
| 'err', |
| 'callMain', |
| 'abort', |
| 'wasmMemory', |
| 'wasmExports', |
| 'HEAPF32', |
| 'HEAPF64', |
| 'HEAP8', |
| 'HEAPU8', |
| 'HEAP16', |
| 'HEAPU16', |
| 'HEAP32', |
| 'HEAPU32', |
| 'HEAP64', |
| 'HEAPU64', |
| ]; |
| |
| if (SUPPORT_BIG_ENDIAN) { |
| runtimeElements.push('HEAP_DATA_VIEW'); |
| } |
| |
| if (USE_OFFSET_CONVERTER) { |
| runtimeElements.push('WasmOffsetConverter'); |
| } |
| |
| if (LOAD_SOURCE_MAP) { |
| runtimeElements.push('WasmSourceMap'); |
| } |
| |
| if (STACK_OVERFLOW_CHECK) { |
| runtimeElements.push('writeStackCookie'); |
| runtimeElements.push('checkStackCookie'); |
| } |
| |
| if (RETAIN_COMPILER_SETTINGS) { |
| runtimeElements.push('getCompilerSetting'); |
| } |
| |
| if (RUNTIME_DEBUG) { |
| runtimeElements.push('prettyPrint'); |
| } |
| |
| // 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) { |
| 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. |
| let runtimeElementsSet = new Set(runtimeElements); |
| for (const ident of Object.keys(LibraryManager.library)) { |
| if (isJsOnlySymbol(ident) && !isDecorator(ident) && !isInternalSymbol(ident)) { |
| const jsname = ident.slice(1); |
| // Note that this assertion may be hit when a function is moved into the |
| // JS library. In that case the function should be removed from the list |
| // of runtime elements above. |
| assert(!runtimeElementsSet.has(jsname), 'runtimeElements contains library symbol: ' + ident); |
| runtimeElements.push(jsname); |
| } |
| } |
| |
| // check all exported things exist, error when missing |
| runtimeElementsSet = new Set(runtimeElements); |
| for (const name of EXPORTED_RUNTIME_METHODS) { |
| if (!runtimeElementsSet.has(name)) { |
| error(`undefined exported symbol: "${name}" in EXPORTED_RUNTIME_METHODS`); |
| } |
| } |
| |
| const exports = runtimeElements.filter(shouldExport); |
| const results = exports.map(exportSymbol); |
| |
| if (MODULARIZE == 'instance') { |
| if (results.length == 0) return ''; |
| return '// Runtime exports\nexport { ' + results.join(', ') + ' };\n'; |
| } |
| |
| if (ASSERTIONS && !EXPORT_ALL) { |
| // in ASSERTIONS mode we show a useful error if it is used without being |
| // exported. See `unexportedRuntimeSymbol` in runtime_debug.js. |
| const unusedLibSymbols = getUnusedLibrarySymbols(); |
| if (unusedLibSymbols.size) { |
| results.push(addMissingLibraryStubs(unusedLibSymbols)); |
| } |
| |
| const unexported = []; |
| for (const name of runtimeElements) { |
| if ( |
| !EXPORTED_RUNTIME_METHODS.has(name) && |
| !EXPORTED_FUNCTIONS.has(name) && |
| !unusedLibSymbols.has(name) |
| ) { |
| unexported.push(name); |
| } |
| } |
| |
| if (unexported.length || unusedLibSymbols.size) { |
| let unexportedStubs = 'var unexportedSymbols = [\n'; |
| for (const sym of unexported) { |
| unexportedStubs += ` '${sym}',\n`; |
| } |
| unexportedStubs += '];\n'; |
| unexportedStubs += 'unexportedSymbols.forEach(unexportedRuntimeSymbol);\n'; |
| results.push(unexportedStubs); |
| } |
| } |
| |
| results.unshift('// Begin runtime exports'); |
| results.push('// End runtime exports'); |
| return results.join('\n ') + '\n'; |
| } |
| |
| function exportLibrarySymbols() { |
| assert(MODULARIZE != 'instance'); |
| const results = ['// Begin JS library exports']; |
| for (const ident of librarySymbols) { |
| if (EXPORT_ALL || EXPORTED_FUNCTIONS.has(ident)) { |
| results.push(exportSymbol(ident)); |
| } |
| } |
| results.push('// End JS library exports'); |
| return results.join('\n ') + '\n'; |
| } |
| |
| function exportJSSymbols() { |
| // In MODULARIZE=instance mode JS library symbols are marked with `export` |
| // at the point of declaration. |
| if (MODULARIZE == 'instance') return exportRuntimeSymbols(); |
| return exportRuntimeSymbols() + ' ' + exportLibrarySymbols(); |
| } |
| |
| addToCompileTimeContext({ |
| exportJSSymbols, |
| loadStructInfo, |
| LibraryManager, |
| librarySymbols, |
| addToLibrary, |
| cDefs, |
| cDefine, |
| C_STRUCTS, |
| C_DEFINES, |
| }); |