| |
| |
| |
| |
| |
|
|
| import * as os from 'node:os'; |
| import * as fs from 'node:fs'; |
| import * as path from 'node:path'; |
| import {fileURLToPath} from 'node:url'; |
| import assert from 'node:assert'; |
|
|
| import { |
| debugLog, |
| isDecorator, |
| isJsOnlySymbol, |
| error, |
| readFile, |
| pushCurrentFile, |
| popCurrentFile, |
| addToCompileTimeContext, |
| runInMacroContext, |
| mergeInto, |
| localFile, |
| timer, |
| } from './utility.mjs'; |
| import {preprocess, processMacros} from './parseTools.mjs'; |
|
|
| |
|
|
| |
| export const librarySymbols = []; |
| |
| |
| export const nativeAliases = {}; |
|
|
| 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() { |
| |
| let libraries = [ |
| 'libint53.js', |
| 'libcore.js', |
| 'libsigs.js', |
| 'libccall.js', |
| 'libaddfunction.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 (SUPPORT_BASE64_EMBEDDING || ENVIRONMENT_MAY_BE_SHELL) { |
| libraries.push('libbase64.js'); |
| } |
|
|
| if (AUTODEBUG) { |
| libraries.push('libautodebug.js'); |
| } |
|
|
| if (!WASMFS) { |
| libraries.push('libsyscall.js'); |
| } |
|
|
| if (MAIN_MODULE) { |
| 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 { |
| |
| libraries.push( |
| 'libfs.js', |
| 'libmemfs.js', |
| 'libtty.js', |
| 'libpipefs.js', |
| 'libsockfs.js', |
| ); |
|
|
| if (NODERAWFS) { |
| |
| libraries.push('libnodefs.js'); |
| libraries.push('libnoderawfs.js'); |
| |
| libraries.push('libnodepath.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) { |
| |
| |
| 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 (!STRICT) { |
| libraries.push('liblegacy.js'); |
| } |
|
|
| if (BOOTSTRAPPING_STRUCT_INFO) { |
| libraries = ['libbootstrap.js', 'libstrings.js', 'libint53.js']; |
| } |
|
|
| if (SUPPORT_BIG_ENDIAN) { |
| libraries.push('liblittle_endian_heap.js'); |
| } |
|
|
| |
| libraries = libraries.map((filename) => path.join(systemLibdir, filename)); |
|
|
| |
| |
| |
| |
| libraries.push(...JS_LIBRARIES); |
|
|
| |
| libraries = [...new Set(libraries)] |
|
|
| return libraries; |
| } |
|
|
| let tempDir; |
|
|
| function getTempDir() { |
| if (!tempDir) { |
| const tempRoot = os.tmpdir(); |
| tempDir = fs.mkdtempSync(path.join(tempRoot, 'emcc-jscompiler-')); |
| } |
| return tempDir; |
| } |
|
|
| function preprocessFiles(filenames) { |
| timer.start('preprocessFiles') |
| const results = {}; |
| for (const filename of filenames) { |
| debugLog(`pre-processing JS library: ${filename}`); |
| pushCurrentFile(filename); |
| try { |
| results[filename] = processMacros(preprocess(filename), filename); |
| } catch (e) { |
| error(`error preprocessing JS library "${filename}":`); |
| throw e; |
| } finally { |
| popCurrentFile(); |
| } |
| } |
| timer.stop('preprocessFiles') |
| return results; |
| } |
|
|
| export const LibraryManager = { |
| library: {}, |
| |
| libraryDefinitions: {}, |
| structs: {}, |
| loaded: false, |
| libraries: [], |
|
|
| has(name) { |
| if (!path.isAbsolute(name)) { |
| |
| |
| |
| if (name.startsWith('library_')) { |
| name = name.replace('library_', 'lib'); |
| } |
| name = path.join(systemLibdir, name); |
| } |
| return this.libraries.includes(name); |
| }, |
|
|
| load() { |
| timer.start('load') |
|
|
| assert(!this.loaded); |
| this.loaded = true; |
| |
| this.libraries = calculateLibraries(); |
|
|
| const preprocessed = preprocessFiles(this.libraries); |
|
|
| timer.start('executeJS') |
| for (const [filename, contents] of Object.entries(preprocessed)) { |
| this.executeJSLibraryFile(filename, contents); |
| } |
| timer.stop('executeJS') |
|
|
| this.addAliasDependencies(); |
|
|
| timer.stop('load') |
| }, |
|
|
| isAlias(entry) { |
| return (typeof entry == 'string' && entry[0] != '=' && (this.library.hasOwnProperty(entry) || WASM_EXPORTS.has(entry))); |
| }, |
|
|
| |
| |
| |
| addAliasDependencies() { |
| const aliases = {}; |
| for (const [key, value] of Object.entries(this.library)) { |
| if (this.isAlias(value)) { |
| aliases[key] = value; |
| } |
| } |
| for (const [key, value] of Object.entries(aliases)) { |
| (this.library[key + '__deps'] ??= []).push(value); |
| } |
| }, |
|
|
| executeJSLibraryFile(filename, contents) { |
| const userLibraryProxy = new Proxy(this.library, { |
| set(target, prop, value) { |
| target[prop] = value; |
| if (!isDecorator(prop)) { |
| target[prop + '__user'] = true; |
| } |
| return true; |
| }, |
| }); |
|
|
| const isUserLibrary = !isBeneath(filename, systemLibdir); |
| if (isUserLibrary) { |
| debugLog(`executing user JS library: ${filename}`); |
| } else { |
| debugLog(`exectuing system JS library: ${filename}`); |
| } |
|
|
| let origLibrary; |
| |
| |
| if (isUserLibrary) { |
| origLibrary = this.library; |
| this.library = userLibraryProxy; |
| } |
| pushCurrentFile(filename); |
| let preprocessedName = filename.replace(/\.\w+$/, '.preprocessed$&') |
| if (VERBOSE) { |
| preprocessedName = path.join(getTempDir(), path.basename(filename)); |
| } |
|
|
| try { |
| runInMacroContext(contents, {filename: preprocessedName}) |
| } catch (e) { |
| error(`failure to execute JS library "${filename}":`); |
| if (VERBOSE) { |
| fs.writeFileSync(preprocessedName, contents); |
| error(`preprocessed JS saved to ${preprocessedName}`) |
| } else { |
| error('use -sVERBOSE to save preprocessed JS'); |
| } |
| throw e; |
| } finally { |
| popCurrentFile(); |
| if (origLibrary) { |
| this.library = origLibrary; |
| } |
| } |
| if (VERBOSE) { |
| fs.rmSync(getTempDir(), { recursive: true, force: true }); |
| } |
| } |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function addToLibrary(obj, options = null) { |
| mergeInto(LibraryManager.library, obj, options); |
| } |
|
|
| let structs = {}; |
| let defines = {}; |
|
|
| |
| |
| |
| |
| function loadStructInfo(filename) { |
| const temp = JSON.parse(readFile(filename)); |
| Object.assign(structs, temp.structs); |
| Object.assign(defines, temp.defines); |
| } |
|
|
| if (!BOOTSTRAPPING_STRUCT_INFO) { |
| |
| if (MEMORY64) { |
| loadStructInfo(localFile('struct_info_generated_wasm64.json')); |
| } else { |
| loadStructInfo(localFile('struct_info_generated.json')); |
| } |
| } |
|
|
| |
| |
| 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]; |
| }, |
| }); |
|
|
| |
| const cDefs = C_DEFINES; |
|
|
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| 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) { |
| |
| |
| if (MODULARIZE === 'instance') { |
| return name; |
| } |
| return `Module['${name}'] = ${name};`; |
| } |
|
|
| |
| function exportRuntimeSymbols() { |
| |
| function shouldExport(name) { |
| |
| |
| if (nativeAliases[name]) { |
| return false; |
| } |
| |
| if (EXPORTED_RUNTIME_METHODS.has(name)) { |
| |
| |
| if (MODULARIZE == 'instance' || !name.startsWith('HEAP')) { |
| return true; |
| } |
| } |
| return false; |
| } |
|
|
| |
| let runtimeElements = [ |
| 'run', |
| 'out', |
| 'err', |
| 'callMain', |
| 'abort', |
| 'wasmExports', |
| ]; |
|
|
| if (SUPPORT_BIG_ENDIAN) { |
| runtimeElements.push('HEAP_DATA_VIEW'); |
| } |
|
|
| 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'); |
| } |
|
|
| |
| |
| |
| for (const name of EXPORTED_RUNTIME_METHODS) { |
| if (/^dynCall_/.test(name)) { |
| |
| runtimeElements.push(name); |
| } |
| } |
|
|
| |
| |
| 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); |
| |
| |
| |
| assert(!runtimeElementsSet.has(jsname), 'runtimeElements contains library symbol: ' + ident); |
| runtimeElements.push(jsname); |
| } |
| } |
|
|
| |
| 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) { |
| |
| |
| 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)) && !nativeAliases[ident]) { |
| results.push(exportSymbol(ident)); |
| } |
| } |
| results.push('// End JS library exports'); |
| return results.join('\n ') + '\n'; |
| } |
|
|
| function exportJSSymbols() { |
| |
| |
| if (MODULARIZE == 'instance') return exportRuntimeSymbols(); |
| return exportRuntimeSymbols() + ' ' + exportLibrarySymbols(); |
| } |
|
|
| addToCompileTimeContext({ |
| exportJSSymbols, |
| loadStructInfo, |
| LibraryManager, |
| librarySymbols, |
| addToLibrary, |
| cDefs, |
| cDefine, |
| C_STRUCTS, |
| C_DEFINES, |
| }); |
|
|