diff --git a/src/jsifier.mjs b/src/jsifier.mjs index 6a05135ce6e5e..43f8c51a5be4a 100644 --- a/src/jsifier.mjs +++ b/src/jsifier.mjs @@ -44,6 +44,7 @@ import { timer, } from './utility.mjs'; import { + addedLibraryItems, extraExports, LibraryManager, librarySymbols, @@ -51,8 +52,6 @@ import { shouldExportLibrarySymbol, } from './modules.mjs'; -const addedLibraryItems = {}; - // Debug-only mode (`EMCC_CHECK_DEPS=1` in the environment) that performs a full // AST parse on JS library items and reports an error if any local binding // shadows a library symbol (which could cause false positives in the @@ -606,7 +605,7 @@ export async function runJSify(outputFile, symbolsOnly) { } for (const key of Object.keys(LibraryManager.library)) { - if (!isDecorator(key)) { + if (!isDecorator(key) && !LibraryManager.library[key + '__const']) { if (INCLUDE_FULL_LIBRARY || EXPORTED_FUNCTIONS.has(mangleCSymbolName(key))) { symbolsNeeded.push(key); } @@ -886,7 +885,10 @@ function(${args}) { snippet = new Function('...args', stubFunctionBody); } - librarySymbols.push(mangled); + const isConst = LibraryManager.library[symbol + '__const']; + if (!isConst) { + librarySymbols.push(mangled); + } if (!isStub && LibraryManager.library[symbol + '__export']) { extraExports.add(mangled); @@ -957,7 +959,7 @@ function(${args}) { // Check for dependencies on `__internal` symbols from user libraries. if (isUserSymbol) { for (const dep of deps) { - if (LibraryManager.library[dep + '__internal']) { + if (LibraryManager.library[dep + '__internal'] && !LibraryManager.library[dep + '__user']) { warn(`user library symbol '${symbol}' depends on internal symbol '${dep}'`); } } @@ -979,6 +981,9 @@ function(${args}) { } let contentText; if (isFunction) { + if (isConst) { + error(`JS library error: '__const' decorator cannot be used with functions: '${symbol}'`); + } // Emit the body of a JS library function. if ((USE_ASAN || USE_LSAN) && LibraryManager.library[symbol + '__noleakcheck']) { contentText = modifyJSFunction( @@ -1003,6 +1008,9 @@ function(${args}) { contentText = contentText.replace(/function(?:\s+([^(]+))?\s*\(/, `function ${mangled}(`); } } else if (typeof snippet === 'string' && snippet.startsWith(';')) { + if (isConst) { + error(`JS library error: '__const' decorator cannot be used with ';'-prefixed snippets: '${symbol}'`); + } // In JS libraries // foo: ';[code here verbatim]' // emits @@ -1012,6 +1020,9 @@ function(${args}) { contentText += ';'; } } else if (typeof snippet === 'undefined') { + if (isConst) { + error(`JS library error: '__const' decorator cannot be used with undefined symbols: '${symbol}'`); + } // 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. @@ -1029,14 +1040,15 @@ function(${args}) { if (typeof snippet === 'string' && snippet[0] == '=') { snippet = snippet.slice(1); } - contentText = `var ${mangled} = ${snippet};`; + const declType = isConst ? 'const' : 'var'; + contentText = `${declType} ${mangled} = ${snippet};`; } if (CHECK_DEPS && (STRICT || !isUserSymbol) && !isStub && contentText) { checkShadowing(symbol, mangled, contentText); } - if (contentText && MODULARIZE == 'instance' && shouldExportLibrarySymbol(mangled) && !isStub) { + if (contentText && MODULARIZE == 'instance' && shouldExportLibrarySymbol(mangled) && !isStub && !isConst) { // In MODULARIZE=instance mode mark JS library symbols are exported at // the point of declaration. contentText = 'export ' + contentText; diff --git a/src/lib/libcore.js b/src/lib/libcore.js index 36958b4c6322f..4d5ed34eded36 100644 --- a/src/lib/libcore.js +++ b/src/lib/libcore.js @@ -1478,13 +1478,14 @@ addToLibrary({ // Represents whether emscripten_get_now is guaranteed monotonic; the Date.now // implementation is not :( - $nowIsMonotonic__internal: true, + ...defineInternalConsts({ #if AUDIO_WORKLET // // https://github.com/WebAudio/web-audio-api/issues/2413 - $nowIsMonotonic: `!!globalThis.performance?.now;`, + $nowIsMonotonic: `!!globalThis.performance?.now;`, #else - // Modern environment where performance.now() is supported - $nowIsMonotonic: 1, + // Modern environment where performance.now() is supported + $nowIsMonotonic: 1, #endif + }), _emscripten_get_now_is_monotonic__internal: true, _emscripten_get_now_is_monotonic: () => nowIsMonotonic, @@ -2410,15 +2411,17 @@ addToLibrary({ // We used to define these globals unconditionally in support code. // Instead, we now define them here so folks can pull it in explicitly, on // demand. - $STACK_SIZE: {{{ STACK_SIZE }}}, - $STACK_ALIGN: {{{ STACK_ALIGN }}}, - $POINTER_SIZE: {{{ POINTER_SIZE }}}, - $ASSERTIONS: {{{ ASSERTIONS }}}, + ...defineConsts({ + $STACK_SIZE: {{{ STACK_SIZE }}}, + $STACK_ALIGN: {{{ STACK_ALIGN }}}, + $POINTER_SIZE: {{{ POINTER_SIZE }}}, + $ASSERTIONS: {{{ ASSERTIONS }}}, + }), }); function autoAddDeps(lib, name) { for (const item of Object.keys(lib)) { - if (!isDecorator(item)) { + if (!isDecorator(item) && item != name && !lib[item + '__const']) { lib[item + '__deps'] ??= []; lib[item + '__deps'].push(name); } diff --git a/src/lib/libdylink.js b/src/lib/libdylink.js index ca593f7d4337f..2920bf3c931cd 100644 --- a/src/lib/libdylink.js +++ b/src/lib/libdylink.js @@ -10,11 +10,10 @@ #error "library_dylink.js requires MAIN_MODULE" #endif -{{{ -const UNDEFINED_ADDR = to64(-1); -}}} - var LibraryDylink = { + ...defineInternalConsts({ + $UNDEFINED_ADDR: to64(-1), + }), #if FILESYSTEM $registerWasmPlugin: () => { // Use string keys here for public methods to avoid minification since the @@ -157,7 +156,7 @@ var LibraryDylink = { #if DYLINK_DEBUG == 2 dbg(`new GOT entry: ${symName}`); #endif - rtn = GOT[symName] = new WebAssembly.Global({'value': '{{{ POINTER_WASM_TYPE }}}', 'mutable': true}, {{{ UNDEFINED_ADDR }}}); + rtn = GOT[symName] = new WebAssembly.Global({'value': '{{{ POINTER_WASM_TYPE }}}', 'mutable': true}, UNDEFINED_ADDR); } if (!weakImports.has(symName)) { // Any non-weak reference to a symbol marks it as `required`, which @@ -219,7 +218,7 @@ var LibraryDylink = { } #endif - var existingEntry = GOT[symName] && GOT[symName].value != {{{ UNDEFINED_ADDR }}}; + var existingEntry = GOT[symName] && GOT[symName].value != UNDEFINED_ADDR; if (replace || !existingEntry) { #if DYLINK_DEBUG == 2 dbg(`updateGOT: before: ${symName} : ${GOT[symName]?.value}`); @@ -303,7 +302,7 @@ var LibraryDylink = { dbg('reportUndefinedSymbols'); #endif for (var [symName, entry] of Object.entries(GOT)) { - if (entry.value == {{{ UNDEFINED_ADDR }}}) { + if (entry.value == UNDEFINED_ADDR) { #if DYLINK_DEBUG dbg(`undef GOT entry: ${symName}`); #endif diff --git a/src/lib/libegl.js b/src/lib/libegl.js index d44692f1b49c2..a17d0f7d03939 100644 --- a/src/lib/libegl.js +++ b/src/lib/libegl.js @@ -15,16 +15,16 @@ * value of 62006. (multiple creations silently return the same surface) */ -{{{ -// Magic ID for Emscripten 'default display' -const eglDefaultDisplay = 62000; -// Magic ID for the only EGLConfig supported by Emscripten -const eglDefaultConfig = 62002; -// Magic ID for Emscripten EGLContext -const eglDefaultContext = 62004; -}}} - var LibraryEGL = { + ...defineInternalConsts({ + // Magic ID for Emscripten 'default display' + $eglDefaultDisplay: 62000, + // Magic ID for the only EGLConfig supported by Emscripten + $eglDefaultConfig: 62002, + // Magic ID for Emscripten EGLContext + $eglDefaultContext: 62004, + }), + $EGL__deps: ['$Browser'], $EGL: { // This variable tracks the success status of the most recently invoked EGL function call. @@ -48,7 +48,7 @@ var LibraryEGL = { }, chooseConfig(display, attribList, config, config_size, numConfigs) { - if (display != {{{ eglDefaultDisplay }}}) { + if (display != eglDefaultDisplay) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); return 0; } @@ -90,7 +90,7 @@ var LibraryEGL = { {{{ makeSetValue('numConfigs', '0', '1', 'i32') }}}; // Total number of supported configs: 1. } if (config && config_size > 0) { - {{{ makeSetValue('config', '0', eglDefaultConfig /* Magic ID for the only EGLConfig supported by Emscripten */, '*') }}}; + {{{ makeSetValue('config', '0', 'eglDefaultConfig' /* Magic ID for the only EGLConfig supported by Emscripten */, '*') }}}; } EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); @@ -108,13 +108,13 @@ var LibraryEGL = { if (nativeDisplayType != 0 /* EGL_DEFAULT_DISPLAY */ && nativeDisplayType != 1 /* see library_xlib.js */) { return 0; // EGL_NO_DISPLAY } - return {{{ eglDefaultDisplay }}}; + return eglDefaultDisplay; }, // EGLAPI EGLBoolean EGLAPIENTRY eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor); eglInitialize__proxy: 'sync', eglInitialize: (display, majorVersion, minorVersion) => { - if (display != {{{ eglDefaultDisplay }}}) { + if (display != eglDefaultDisplay) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); return 0; } @@ -132,7 +132,7 @@ var LibraryEGL = { // EGLAPI EGLBoolean EGLAPIENTRY eglTerminate(EGLDisplay dpy); eglTerminate__proxy: 'sync', eglTerminate: (display) => { - if (display != {{{ eglDefaultDisplay }}}) { + if (display != eglDefaultDisplay) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); return 0; } @@ -157,11 +157,11 @@ var LibraryEGL = { // EGLAPI EGLBoolean EGLAPIENTRY eglGetConfigAttrib(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value); eglGetConfigAttrib__proxy: 'sync', eglGetConfigAttrib: (display, config, attribute, value) => { - if (display != {{{ eglDefaultDisplay }}}) { + if (display != eglDefaultDisplay) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); return 0; } - if (config != {{{ eglDefaultConfig }}}) { + if (config != eglDefaultConfig) { EGL.setErrorCode(0x3005 /* EGL_BAD_CONFIG */); return 0; } @@ -197,7 +197,7 @@ var LibraryEGL = { {{{ makeSetValue('value', '0', '0x3038' /* EGL_NONE */, 'i32') }}}; return 1; case 0x3028: // EGL_CONFIG_ID - {{{ makeSetValue('value', '0', eglDefaultConfig, 'i32') }}}; + {{{ makeSetValue('value', '0', 'eglDefaultConfig', 'i32') }}}; return 1; case 0x3029: // EGL_LEVEL {{{ makeSetValue('value', '0', '0' /* Z order/depth layer for this level. Not applicable for Emscripten. */, 'i32') }}}; @@ -274,11 +274,11 @@ var LibraryEGL = { // EGLAPI EGLSurface EGLAPIENTRY eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list); eglCreateWindowSurface__proxy: 'sync', eglCreateWindowSurface: (display, config, win, attrib_list) => { - if (display != {{{ eglDefaultDisplay }}}) { + if (display != eglDefaultDisplay) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); return 0; } - if (config != {{{ eglDefaultConfig }}}) { + if (config != eglDefaultConfig) { EGL.setErrorCode(0x3005 /* EGL_BAD_CONFIG */); return 0; } @@ -293,7 +293,7 @@ var LibraryEGL = { // EGLAPI EGLBoolean EGLAPIENTRY eglDestroySurface(EGLDisplay display, EGLSurface surface); eglDestroySurface__proxy: 'sync', eglDestroySurface: (display, surface) => { - if (display != {{{ eglDefaultDisplay }}}) { + if (display != eglDefaultDisplay) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); return 0; } @@ -315,7 +315,7 @@ var LibraryEGL = { // EGLAPI EGLContext EGLAPIENTRY eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list); eglCreateContext__proxy: 'sync', eglCreateContext: (display, config, hmm, contextAttribs) => { - if (display != {{{ eglDefaultDisplay }}}) { + if (display != eglDefaultDisplay) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); return 0; } @@ -367,7 +367,7 @@ var LibraryEGL = { // Note: This function only creates a context, but it shall not make it active. GL.makeContextCurrent(null); - return {{{ eglDefaultContext }}}; + return eglDefaultContext; } else { EGL.setErrorCode(0x3009 /* EGL_BAD_MATCH */); // By the EGL 1.4 spec, an implementation that does not support GLES2 (WebGL in this case), this error code is set. return 0; /* EGL_NO_CONTEXT */ @@ -378,11 +378,11 @@ var LibraryEGL = { // EGLAPI EGLBoolean EGLAPIENTRY eglDestroyContext(EGLDisplay dpy, EGLContext context); eglDestroyContext__proxy: 'sync', eglDestroyContext: (display, context) => { - if (display != {{{ eglDefaultDisplay }}}) { + if (display != eglDefaultDisplay) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); return 0; } - if (context != {{{ eglDefaultContext }}}) { + if (context != eglDefaultContext) { EGL.setErrorCode(0x3006 /* EGL_BAD_CONTEXT */); return 0; } @@ -398,7 +398,7 @@ var LibraryEGL = { // EGLAPI EGLBoolean EGLAPIENTRY eglQuerySurface(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint *value); eglQuerySurface__proxy: 'sync', eglQuerySurface: (display, surface, attribute, value) => { - if (display != {{{ eglDefaultDisplay }}}) { + if (display != eglDefaultDisplay) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); return 0; } @@ -413,7 +413,7 @@ var LibraryEGL = { EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); switch (attribute) { case 0x3028: // EGL_CONFIG_ID - {{{ makeSetValue('value', '0', eglDefaultConfig, 'i32') }}}; + {{{ makeSetValue('value', '0', 'eglDefaultConfig', 'i32') }}}; return 1; case 0x3058: // EGL_LARGEST_PBUFFER // Odd EGL API: If surface is not a pbuffer surface, 'value' should not be written to. It's not specified as an error, so true should(?) be returned. @@ -464,12 +464,12 @@ var LibraryEGL = { // EGLAPI EGLBoolean EGLAPIENTRY eglQueryContext(EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint *value); eglQueryContext__proxy: 'sync', eglQueryContext: (display, context, attribute, value) => { - if (display != {{{ eglDefaultDisplay }}}) { + if (display != eglDefaultDisplay) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); return 0; } //\todo An EGL_NOT_INITIALIZED error is generated if EGL is not initialized for dpy. - if (context != {{{ eglDefaultContext }}}) { + if (context != eglDefaultContext) { EGL.setErrorCode(0x3006 /* EGL_BAD_CONTEXT */); return 0; } @@ -481,7 +481,7 @@ var LibraryEGL = { EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); switch (attribute) { case 0x3028: // EGL_CONFIG_ID - {{{ makeSetValue('value', '0', eglDefaultConfig, 'i32') }}}; + {{{ makeSetValue('value', '0', 'eglDefaultConfig', 'i32') }}}; return 1; case 0x3097: // EGL_CONTEXT_CLIENT_TYPE {{{ makeSetValue('value', '0', '0x30A0' /* EGL_OPENGL_ES_API */, 'i32') }}}; @@ -509,7 +509,7 @@ var LibraryEGL = { eglQueryString__noleakcheck: true, eglQueryString__proxy: 'sync', eglQueryString: (display, name) => { - if (display != {{{ eglDefaultDisplay }}}) { + if (display != eglDefaultDisplay) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); return 0; } @@ -570,7 +570,7 @@ var LibraryEGL = { // EGLAPI EGLBoolean EGLAPIENTRY eglSwapInterval(EGLDisplay dpy, EGLint interval); eglSwapInterval__proxy: 'sync', eglSwapInterval: (display, interval) => { - if (display != {{{ eglDefaultDisplay }}}) { + if (display != eglDefaultDisplay) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); return 0; } @@ -584,12 +584,12 @@ var LibraryEGL = { // EGLAPI EGLBoolean EGLAPIENTRY eglMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx); eglMakeCurrent__proxy: 'sync', eglMakeCurrent: (display, draw, read, context) => { - if (display != {{{ eglDefaultDisplay }}}) { + if (display != eglDefaultDisplay) { EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); return 0 /* EGL_FALSE */; } //\todo An EGL_NOT_INITIALIZED error is generated if EGL is not initialized for dpy. - if (context != 0 && context != {{{ eglDefaultContext }}}) { + if (context != 0 && context != eglDefaultContext) { EGL.setErrorCode(0x3006 /* EGL_BAD_CONTEXT */); return 0; } @@ -626,7 +626,7 @@ var LibraryEGL = { // EGLAPI EGLDisplay EGLAPIENTRY eglGetCurrentDisplay(void); eglGetCurrentDisplay__proxy: 'sync', - eglGetCurrentDisplay: () => EGL.currentContext ? {{{ eglDefaultDisplay }}} : 0, + eglGetCurrentDisplay: () => EGL.currentContext ? eglDefaultDisplay : 0, // EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffers(EGLDisplay dpy, EGLSurface surface); eglSwapBuffers__proxy: 'sync', diff --git a/src/lib/libemval.js b/src/lib/libemval.js index 498da14c3f578..7d76f8d19797d 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -3,12 +3,15 @@ // University of Illinois/NCSA Open Source License. Both these licenses can be // found in the LICENSE file. -// Number of handles reserved for non-use (0) or common values w/o refcount. -{{{ - const EMVAL_RESERVED_HANDLES = 5; - const EMVAL_LAST_RESERVED_HANDLE = EMVAL_RESERVED_HANDLES * 2 - 1; -}}} +const NUM_RESERVED_HANDLES = 5; + var LibraryEmVal = { + // Number of handles reserved for non-use (0) or common values w/o refcount. + ...defineInternalConsts({ + $EMVAL_RESERVED_HANDLES: NUM_RESERVED_HANDLES, + $EMVAL_LAST_RESERVED_HANDLE: NUM_RESERVED_HANDLES * 2 - 1 + }), + // Stack of handles available for reuse. $emval_freelist: [], #if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS @@ -24,12 +27,12 @@ var LibraryEmVal = { false, 1, ], #if ASSERTIONS - $emval_handles__postset: 'assert(emval_handles.length === {{{ EMVAL_RESERVED_HANDLES }}} * 2)', + $emval_handles__postset: 'assert(emval_handles.length === EMVAL_RESERVED_HANDLES * 2)', #endif $emval_symbols: {}, // address -> string $count_emval_handles: () => { - return emval_handles.length / 2 - {{{ EMVAL_RESERVED_HANDLES }}} - emval_freelist.length; + return emval_handles.length / 2 - EMVAL_RESERVED_HANDLES - emval_freelist.length; }, _emval_register_symbol: (address) => { @@ -73,13 +76,13 @@ var LibraryEmVal = { }, _emval_incref: (handle) => { - if (handle > {{{ EMVAL_LAST_RESERVED_HANDLE }}}) { + if (handle > EMVAL_LAST_RESERVED_HANDLE) { emval_handles[handle + 1] += 1; } }, _emval_decref: (handle) => { - if (handle > {{{ EMVAL_LAST_RESERVED_HANDLE }}} && 0 === --emval_handles[handle + 1]) { + if (handle > EMVAL_LAST_RESERVED_HANDLE && 0 === --emval_handles[handle + 1]) { #if ASSERTIONS assert(emval_handles[handle] !== undefined, `decref for unallocated handle`); #endif diff --git a/src/lib/libint53.js b/src/lib/libint53.js index ee06b3eab08de..c8c425fcb6b23 100644 --- a/src/lib/libint53.js +++ b/src/lib/libint53.js @@ -119,8 +119,10 @@ addToLibrary({ }, #if WASM_BIGINT - $INT53_MAX: '{{{ Math.pow(2, 53) }}}', - $INT53_MIN: '-{{{ Math.pow(2, 53) }}}', + ...defineConsts({ + $INT53_MAX: 2 ** 53, + $INT53_MIN: -(2 ** 53), + }), // Convert a bigint value (usually coming from Wasm->JS call) into an int53 // JS Number. This is used when we have an incoming i64 that we know is a // pointer or size_t and is expected to be within the int53 range. diff --git a/src/lib/libpthread.js b/src/lib/libpthread.js index eabb60ac34f87..fdfcb81ce1b29 100644 --- a/src/lib/libpthread.js +++ b/src/lib/libpthread.js @@ -24,23 +24,6 @@ #endif {{{ -#if MEMORY64 -const MAX_PTR = Number((2n ** 64n) - 1n); -#else -const MAX_PTR = (2 ** 32) - 1 -#endif - -// Message IDs used when communicating with workers via postMessage. -const CMD_LOAD = 1; -const CMD_RUN = 2; -const CMD_LOADED = 3; -const CMD_CHECK_MAILBOX = 4; -const CMD_SPAWN_THREAD = 5; -const CMD_CLEANUP_THREAD = 6; -const CMD_MARK_AS_FINISHED = 7; -const CMD_UNCAUGHT_EXN = 8; -const CMD_CALL_HANDLER = 9; - #if WASM_ESM_INTEGRATION const pthreadWorkerScript = TARGET_BASENAME + '.pthread.mjs'; #else @@ -79,6 +62,22 @@ const pthreadWorkerOptions = `{ }}} var LibraryPThread = { + // Message IDs used when communicating with workers via postMessage. + ...defineInternalConsts({ + $CMD_LOAD: 1, + $CMD_RUN: 2, + $CMD_LOADED: 3, + $CMD_CHECK_MAILBOX: 4, + $CMD_SPAWN_THREAD: 5, + $CMD_CLEANUP_THREAD: 6, + $CMD_MARK_AS_FINISHED: 7, + $CMD_UNCAUGHT_EXN: 8, + $CMD_CALL_HANDLER: 9, +#if OFFSCREENCANVASES_TO_PTHREAD + $MAX_PTR: MEMORY64 ? Number((2n ** 64n) - 1n) : (2 ** 32) - 1, +#endif + }), + $PThread__postset: 'PThread.init();', $PThread__deps: [ '_emscripten_thread_init', @@ -298,24 +297,24 @@ var LibraryPThread = { } switch (cmd) { - case {{{ CMD_CHECK_MAILBOX }}}: + case CMD_CHECK_MAILBOX: checkMailbox(); break; - case {{{ CMD_SPAWN_THREAD }}}: + case CMD_SPAWN_THREAD: spawnThread(d); break; - case {{{ CMD_CLEANUP_THREAD }}}: + case CMD_CLEANUP_THREAD: // cleanupThread needs to be run via callUserCallback since it calls // back into user code to free thread data. Without this it's possible // the unwind or ExitStatus exception could escape here. callUserCallback(() => cleanupThread(d.thread)); break; #if MAIN_MODULE - case {{{ CMD_MARK_AS_FINISHED }}}: + case CMD_MARK_AS_FINISHED: markAsFinished(d.thread); break; #endif - case {{{ CMD_LOADED }}}: + case CMD_LOADED: #if ENVIRONMENT_MAY_BE_NODE if (ENVIRONMENT_IS_NODE && !worker.strongref) { // Once worker is loaded & idle, mark it as weakly referenced, @@ -327,14 +326,14 @@ var LibraryPThread = { onFinishedLoading(worker); break; #if ENVIRONMENT_MAY_BE_NODE - case {{{ CMD_UNCAUGHT_EXN }}}: + case CMD_UNCAUGHT_EXN: // Message handler for Node.js specific out-of-order behavior: // https://github.com/nodejs/node/issues/59617 // A pthread sent an uncaught exception event. Re-raise it on the main thread. worker.onerror(d.error); break; #endif - case {{{ CMD_CALL_HANDLER }}}: + case CMD_CALL_HANDLER: Module[d.handler](...d.args); break; default: @@ -414,7 +413,7 @@ var LibraryPThread = { // Ask the new worker to load up the Emscripten-compiled page. This is a heavy operation. worker.postMessage({ - cmd: {{{ CMD_LOAD }}}, + cmd: CMD_LOAD, handlers: handlers, #if WASM2JS // the polyfill WebAssembly.Memory instance has function properties, @@ -611,7 +610,7 @@ var LibraryPThread = { dbg(`_emscripten_thread_cleanup: ${ptrToString(thread)}`) #endif if (!ENVIRONMENT_IS_PTHREAD) cleanupThread(thread); - else postMessage({ cmd: {{{ CMD_CLEANUP_THREAD }}}, thread }); + else postMessage({ cmd: CMD_CLEANUP_THREAD, thread }); }, _emscripten_thread_set_strongref: (thread) => { @@ -717,7 +716,7 @@ var LibraryPThread = { worker.pthread_ptr = threadParams.pthread_ptr; var msg = { - cmd: {{{ CMD_RUN }}}, + cmd: CMD_RUN, start_routine: threadParams.startRoutine, arg: threadParams.arg, pthread_ptr: threadParams.pthread_ptr, @@ -803,7 +802,7 @@ var LibraryPThread = { // Proxied canvases string pointer -1/MAX_PTR is used as a special token to // fetch whatever canvases were passed to build in // -sOFFSCREENCANVASES_TO_PTHREAD= command line. - if (transferredCanvasNames == {{{ MAX_PTR }}}) { + if (transferredCanvasNames == MAX_PTR) { transferredCanvasNames = '{{{ OFFSCREENCANVASES_TO_PTHREAD }}}'; } else #endif @@ -930,7 +929,7 @@ var LibraryPThread = { // The prepopulated pool of web workers that can host pthreads is stored // in the main JS thread. Therefore if a pthread is attempting to spawn a // new thread, the thread creation must be deferred to the main JS thread. - threadParams.cmd = {{{ CMD_SPAWN_THREAD }}}; + threadParams.cmd = CMD_SPAWN_THREAD; postMessage(threadParams, transferList); // When we defer thread creation this way, we have no way to detect thread // creation synchronously today, so we have to assume success and return 0. @@ -1195,7 +1194,7 @@ var LibraryPThread = { // running, but remain around waiting to be joined. In this state they // cannot run any more proxied work. if (!ENVIRONMENT_IS_PTHREAD) markAsFinished(thread); - else postMessage({ cmd: {{{ CMD_MARK_AS_FINISHED }}}, thread }); + else postMessage({ cmd: CMD_MARK_AS_FINISHED, thread }); }, _emscripten_thread_free_tls: () => PThread.tlsFreeFunctions.forEach((f) => f()), @@ -1332,7 +1331,7 @@ var LibraryPThread = { if (targetThread == currThreadId) { setTimeout(checkMailbox); } else if (ENVIRONMENT_IS_PTHREAD) { - postMessage({targetThread, cmd: {{{ CMD_CHECK_MAILBOX }}}}); + postMessage({targetThread, cmd: CMD_CHECK_MAILBOX}); } else { var worker = PThread.pthreads[targetThread]; if (!worker) { @@ -1341,7 +1340,7 @@ var LibraryPThread = { #endif return; } - worker.postMessage({cmd: {{{ CMD_CHECK_MAILBOX }}}}); + worker.postMessage({cmd: CMD_CHECK_MAILBOX}); } } }; diff --git a/src/lib/libsockfs_node.js b/src/lib/libsockfs_node.js index cc0344dafe0a9..cb55db0ce41e9 100644 --- a/src/lib/libsockfs_node.js +++ b/src/lib/libsockfs_node.js @@ -43,18 +43,17 @@ // shared) wasm memory before being handed to node, so a SharedArrayBuffer heap // is safe. -{{{ -// sock.state lifecycle, substituted as numbers at build time. A fresh socket -// has an undefined (falsy) state until its first bind/connect. -const SOCK_STATE_CONNECTING = 1; -const SOCK_STATE_CONNECTED = 2; -const SOCK_STATE_BOUND = 3; -const SOCK_STATE_LISTEN = 4; -const SOCK_STATE_CLOSED = 5; -null; -}}} - var NodeSockFSLibrary = { + // sock.state lifecycle. A fresh socket has an undefined (falsy) state until + // its first bind/connect. + ...defineInternalConsts({ + $SOCK_STATE_CONNECTING: 1, + $SOCK_STATE_CONNECTED: 2, + $SOCK_STATE_BOUND: 3, + $SOCK_STATE_LISTEN: 4, + $SOCK_STATE_CLOSED: 5, + }), + // Node plumbing shared by the interface methods below. $nodeSockHelpers: { // node builtins, resolved once each. getBuiltinModule works in both @@ -434,7 +433,7 @@ var NodeSockFSLibrary = { }); conn.on('close', () => { sock.readClosed = true; - sock.state = {{{ SOCK_STATE_CLOSED }}}; + sock.state = SOCK_STATE_CLOSED; SOCKFS.emit('close', sock.stream.fd); }); // Backpressure relieved, so we are writable again. @@ -445,7 +444,7 @@ var NodeSockFSLibrary = { conn.on('error', (e) => { sock.error = nodeSockHelpers.nodeErrToErrno(e); // Let a failed connect resolve so SO_ERROR can be read. - if (sock.state === {{{ SOCK_STATE_CONNECTING }}}) sock.state = {{{ SOCK_STATE_CONNECTED }}}; + if (sock.state === SOCK_STATE_CONNECTING) sock.state = SOCK_STATE_CONNECTED; SOCKFS.emit('error', [sock.stream.fd, sock.error, (e && e.message) || 'socket error']); }); }, @@ -475,7 +474,7 @@ var NodeSockFSLibrary = { // POLLERR|POLLHUP, plus writable so SO_ERROR can be read. POLLOUT|POLLERR // also satisfies epoll's is_write_closed() mapping. mask |= {{{ cDefs.POLLOUT }}} | {{{ cDefs.POLLERR }}} | {{{ cDefs.POLLHUP }}}; - } else if (sock.connection && sock.state === {{{ SOCK_STATE_CONNECTED }}} && !sock.writeBlocked) { + } else if (sock.connection && sock.state === SOCK_STATE_CONNECTED && !sock.writeBlocked) { mask |= {{{ cDefs.POLLOUT }}}; } // A peer FIN / read-side hangup (recv will see EOF) is POLLRDHUP. POLLHUP @@ -483,7 +482,7 @@ var NodeSockFSLibrary = { // we locally shut down both directions (shutdown(SHUT_RDWR)), which Linux // epoll reports as a hangup even though the node connection is still live. if (sock.readClosed) mask |= {{{ cDefs.POLLRDHUP }}}; - if (sock.state === {{{ SOCK_STATE_CLOSED }}} || (sock.readClosed && sock.writeShutdown)) { + if (sock.state === SOCK_STATE_CLOSED || (sock.readClosed && sock.writeShutdown)) { mask |= {{{ cDefs.POLLHUP }}}; } return mask; @@ -504,7 +503,7 @@ var NodeSockFSLibrary = { } }, close(sock) { - sock.state = {{{ SOCK_STATE_CLOSED }}}; + sock.state = SOCK_STATE_CLOSED; if (sock.udp) { try { if (sock.udpPublic) sock.udp.close(); @@ -562,7 +561,7 @@ var NodeSockFSLibrary = { // addr is a filesystem path (or an abstract '\0...' name). Bind // synchronously so EADDRINUSE surfaces here and getsockname() works. nodeSockHelpers.bindPipe(sock, addr); - sock.state = {{{ SOCK_STATE_BOUND }}}; + sock.state = SOCK_STATE_BOUND; return; } if (sock.type === {{{ cDefs.SOCK_DGRAM }}}) { @@ -589,7 +588,7 @@ var NodeSockFSLibrary = { sock.saddr = uname.address; sock.sport = uname.port; } - sock.state = {{{ SOCK_STATE_BOUND }}}; + sock.state = SOCK_STATE_BOUND; nodeSockHelpers.startUdpRecv(sock); return; } @@ -597,21 +596,21 @@ var NodeSockFSLibrary = { // a bind(:0)) is known immediately, getsockname() needs no promotion, and a // conflict surfaces right here as EADDRINUSE. nodeSockHelpers.bindHandle(sock, addr, port); - sock.state = {{{ SOCK_STATE_BOUND }}}; + sock.state = SOCK_STATE_BOUND; }, connect(sock, addr, port) { if (sock.family === {{{ cDefs.AF_UNIX }}}) { if (sock.server) throw new FS.ErrnoError({{{ cDefs.EOPNOTSUPP }}}); if (sock.connection) { - throw new FS.ErrnoError(sock.state === {{{ SOCK_STATE_CONNECTING }}} ? {{{ cDefs.EALREADY }}} : {{{ cDefs.EISCONN }}}); + throw new FS.ErrnoError(sock.state === SOCK_STATE_CONNECTING ? {{{ cDefs.EALREADY }}} : {{{ cDefs.EISCONN }}}); } // addr is the peer path. node reports no name back, so record it as the // peer name ourselves; the local end is unnamed unless bind() named it. sock.daddr = addr; - sock.state = {{{ SOCK_STATE_CONNECTING }}}; + sock.state = SOCK_STATE_CONNECTING; var uconn = new (nodeSockHelpers.getNet().Socket)({ allowHalfOpen: true }); uconn.once('connect', () => { - sock.state = {{{ SOCK_STATE_CONNECTED }}}; + sock.state = SOCK_STATE_CONNECTED; sock.saddr ??= ''; try { uconn.resume(); } catch (e) {} SOCKFS.emit('open', sock.stream.fd); @@ -651,11 +650,11 @@ var NodeSockFSLibrary = { } if (sock.server) throw new FS.ErrnoError({{{ cDefs.EOPNOTSUPP }}}); if (sock.connection) { - throw new FS.ErrnoError(sock.state === {{{ SOCK_STATE_CONNECTING }}} ? {{{ cDefs.EALREADY }}} : {{{ cDefs.EISCONN }}}); + throw new FS.ErrnoError(sock.state === SOCK_STATE_CONNECTING ? {{{ cDefs.EALREADY }}} : {{{ cDefs.EISCONN }}}); } sock.daddr = addr; sock.dport = port; - sock.state = {{{ SOCK_STATE_CONNECTING }}}; + sock.state = SOCK_STATE_CONNECTING; var net = nodeSockHelpers.getNet(); if (!sock.bound) { // The kernel assigns the ephemeral source port synchronously at @@ -668,7 +667,7 @@ var NodeSockFSLibrary = { // honored by the kernel. var conn = new net.Socket({ handle: sock.bound, pauseOnCreate: true, allowHalfOpen: true }); conn.once('connect', () => { - sock.state = {{{ SOCK_STATE_CONNECTED }}}; + sock.state = SOCK_STATE_CONNECTED; sock.saddr = conn.localAddress; sock.sport = conn.localPort; sock.daddr = conn.remoteAddress || addr; @@ -698,14 +697,14 @@ var NodeSockFSLibrary = { // bind is eager and synchronous (bindHandle), so the assigned port is // known and any conflict surfaces before we listen. nodeSockHelpers.bindHandle(sock, '0.0.0.0', 0); - sock.state = {{{ SOCK_STATE_BOUND }}}; + sock.state = SOCK_STATE_BOUND; } var server = new (nodeSockHelpers.getNet().Server)({ pauseOnConnect: true, allowHalfOpen: true }); sock.server = server; - sock.state = {{{ SOCK_STATE_LISTEN }}}; + sock.state = SOCK_STATE_LISTEN; server.on('connection', (conn) => { var newsock = SOCKFS.createSocket(sock.family, sock.type, sock.protocol); - newsock.state = {{{ SOCK_STATE_CONNECTED }}}; + newsock.state = SOCK_STATE_CONNECTED; if (isUnix) { // node reports no name for a pipe: the accepted socket's local name is // the listener's path, the peer is unnamed (the client rarely binds). @@ -785,7 +784,7 @@ var NodeSockFSLibrary = { throw new FS.ErrnoError({{{ cDefs.EPIPE }}}); } var conn = sock.connection; - if (!conn || sock.state === {{{ SOCK_STATE_CLOSED }}}) { + if (!conn || sock.state === SOCK_STATE_CLOSED) { throw new FS.ErrnoError({{{ cDefs.ENOTCONN }}}); } // Bound node's write buffer to its high-water mark: a non-blocking socket @@ -1019,7 +1018,7 @@ var NodeSockFSLibrary = { // TCP_MAXSEG: node exposes no MSS, so report RFC 879's 536-byte default // before the handshake and the (large) loopback-negotiated value once // connected. Enough for callers that only compare pre/post-connect MSS. - case 2: val = (sock.state === {{{ SOCK_STATE_CONNECTED }}}) ? 65483 : 536; break; + case 2: val = (sock.state === SOCK_STATE_CONNECTED) ? 65483 : 536; break; case 1: val = sock.opts.noDelay ? 1 : 0; break; // TCP_NODELAY case 4: val = sock.opts.keepAliveIdle || 0; break; // TCP_KEEPIDLE case 5: val = sock.opts.keepAliveIntvl || 0; break;// TCP_KEEPINTVL diff --git a/src/lib/libwasm_worker.js b/src/lib/libwasm_worker.js index 778638905d059..3e1884122a848 100644 --- a/src/lib/libwasm_worker.js +++ b/src/lib/libwasm_worker.js @@ -22,10 +22,6 @@ #endif // ~WASM_WORKERS {{{ -#if !PTHREADS - // In pthread builds this gets defined in libpthread.js - const CMD_UNCAUGHT_EXN = 8; -#endif const workerSupportsFutexWait = () => AUDIO_WORKLET ? '!ENVIRONMENT_IS_AUDIO_WORKLET' : '1'; const wasmWorkerJs = ` #if MINIMAL_RUNTIME @@ -62,6 +58,12 @@ addToLibrary({ +#if !PTHREADS + ...defineInternalConsts({ + $CMD_UNCAUGHT_EXN: 8, + }), +#endif + $_wasmWorkers: {}, #if TRUSTED_TYPES // Cached Trusted Types policy for Wasm Worker creation. @@ -210,7 +212,7 @@ if (ENVIRONMENT_IS_WASM_WORKER if (ENVIRONMENT_IS_NODE) { /** @suppress {checkTypes} */ worker.on('message', (msg) => { - if (msg.cmd == {{{ CMD_UNCAUGHT_EXN }}}) { + if (msg.cmd == CMD_UNCAUGHT_EXN) { // Message handler for Node.js specific out-of-order behavior: // https://github.com/nodejs/node/issues/59617 // A worker sent an uncaught exception event. Re-raise it on the main thread. diff --git a/src/lib/libwebgl.js b/src/lib/libwebgl.js index d90d3b2a9ba18..03e485973de83 100644 --- a/src/lib/libwebgl.js +++ b/src/lib/libwebgl.js @@ -4,11 +4,7 @@ * SPDX-License-Identifier: MIT */ -// Specifies the size of the GL temp buffer pool, in bytes. Must be a multiple -// of 9 and 16. {{{ - const GL_POOL_TEMP_BUFFERS_SIZE = 2*9*16 // = 288 - const isCurrentContextWebGL2 = () => { // This function should only be called inside of `#if MAX_WEBGL_VERSION >= 2` blocks assert(MAX_WEBGL_VERSION >= 2, 'isCurrentContextWebGL2 called without webgl2 support'); @@ -18,6 +14,12 @@ }}} var LibraryGL = { + // Specifies the size of the GL temp buffer pool, in bytes. Must be a multiple + // of 9 and 16. + ...defineInternalConsts({ + $GL_POOL_TEMP_BUFFERS_SIZE: 2 * 9 * 16, // = 288 + }), + // For functions such as glDrawBuffers, glInvalidateFramebuffer and // glInvalidateSubFramebuffer that need to pass a short array to the WebGL // API, create a set of short fixed-length arrays to avoid having to generate @@ -26,16 +28,16 @@ var LibraryGL = { $tempFixedLengthArray: [], $miniTempWebGLFloatBuffers: [], - $miniTempWebGLFloatBuffers__postset: `var miniTempWebGLFloatBuffersStorage = new Float32Array({{{ GL_POOL_TEMP_BUFFERS_SIZE }}}); + $miniTempWebGLFloatBuffers__postset: `var miniTempWebGLFloatBuffersStorage = new Float32Array(GL_POOL_TEMP_BUFFERS_SIZE); // Create GL_POOL_TEMP_BUFFERS_SIZE+1 temporary buffers, for uploads of size 0 through GL_POOL_TEMP_BUFFERS_SIZE inclusive -for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; ++i) { +for (/**@suppress{duplicate}*/var i = 0; i <= GL_POOL_TEMP_BUFFERS_SIZE; ++i) { miniTempWebGLFloatBuffers[i] = miniTempWebGLFloatBuffersStorage.subarray(0, i); }`, $miniTempWebGLIntBuffers: [], - $miniTempWebGLIntBuffers__postset: `var miniTempWebGLIntBuffersStorage = new Int32Array({{{ GL_POOL_TEMP_BUFFERS_SIZE }}}); + $miniTempWebGLIntBuffers__postset: `var miniTempWebGLIntBuffersStorage = new Int32Array(GL_POOL_TEMP_BUFFERS_SIZE); // Create GL_POOL_TEMP_BUFFERS_SIZE+1 temporary buffers, for uploads of size 0 through GL_POOL_TEMP_BUFFERS_SIZE inclusive -for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; ++i) { +for (/**@suppress{duplicate}*/var i = 0; i <= GL_POOL_TEMP_BUFFERS_SIZE; ++i) { miniTempWebGLIntBuffers[i] = miniTempWebGLIntBuffersStorage.subarray(0, i); }`, @@ -2438,7 +2440,7 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; #endif #if GL_POOL_TEMP_BUFFERS - if (count <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}) { + if (count <= GL_POOL_TEMP_BUFFERS_SIZE) { // avoid allocation when uploading few enough uniforms var view = miniTempWebGLIntBuffers[count]; for (var i = 0; i < count; ++i) { @@ -2474,7 +2476,7 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; #endif #if GL_POOL_TEMP_BUFFERS - if (count <= {{{ GL_POOL_TEMP_BUFFERS_SIZE / 2 }}}) { + if (count <= GL_POOL_TEMP_BUFFERS_SIZE / 2) { // avoid allocation when uploading few enough uniforms count *= 2; var view = miniTempWebGLIntBuffers[count]; @@ -2512,7 +2514,7 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; #endif #if GL_POOL_TEMP_BUFFERS - if (count <= {{{ GL_POOL_TEMP_BUFFERS_SIZE / 3 }}}) { + if (count <= GL_POOL_TEMP_BUFFERS_SIZE / 3) { // avoid allocation when uploading few enough uniforms count *= 3; var view = miniTempWebGLIntBuffers[count]; @@ -2551,7 +2553,7 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; #endif #if GL_POOL_TEMP_BUFFERS - if (count <= {{{ GL_POOL_TEMP_BUFFERS_SIZE / 4 }}}) { + if (count <= GL_POOL_TEMP_BUFFERS_SIZE / 4) { // avoid allocation when uploading few enough uniforms count *= 4; var view = miniTempWebGLIntBuffers[count]; @@ -2591,7 +2593,7 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; #endif #if GL_POOL_TEMP_BUFFERS - if (count <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}) { + if (count <= GL_POOL_TEMP_BUFFERS_SIZE) { // avoid allocation when uploading few enough uniforms var view = miniTempWebGLFloatBuffers[count]; for (var i = 0; i < count; ++i) { @@ -2627,7 +2629,7 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; #endif #if GL_POOL_TEMP_BUFFERS - if (count <= {{{ GL_POOL_TEMP_BUFFERS_SIZE / 2 }}}) { + if (count <= GL_POOL_TEMP_BUFFERS_SIZE / 2) { // avoid allocation when uploading few enough uniforms count *= 2; var view = miniTempWebGLFloatBuffers[count]; @@ -2665,7 +2667,7 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; #endif #if GL_POOL_TEMP_BUFFERS - if (count <= {{{ GL_POOL_TEMP_BUFFERS_SIZE / 3 }}}) { + if (count <= GL_POOL_TEMP_BUFFERS_SIZE / 3) { // avoid allocation when uploading few enough uniforms count *= 3; var view = miniTempWebGLFloatBuffers[count]; @@ -2704,7 +2706,7 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; #endif #if GL_POOL_TEMP_BUFFERS - if (count <= {{{ GL_POOL_TEMP_BUFFERS_SIZE / 4 }}}) { + if (count <= GL_POOL_TEMP_BUFFERS_SIZE / 4) { // avoid allocation when uploading few enough uniforms var view = miniTempWebGLFloatBuffers[4*count]; // hoist the heap out of the loop for size and for pthreads+growth. @@ -2748,7 +2750,7 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; #endif #if GL_POOL_TEMP_BUFFERS - if (count <= {{{ GL_POOL_TEMP_BUFFERS_SIZE / 4 }}}) { + if (count <= GL_POOL_TEMP_BUFFERS_SIZE / 4) { // avoid allocation when uploading few enough uniforms count *= 4; var view = miniTempWebGLFloatBuffers[count]; @@ -2788,7 +2790,7 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; #endif #if GL_POOL_TEMP_BUFFERS - if (count <= {{{ GL_POOL_TEMP_BUFFERS_SIZE / 9 }}}) { + if (count <= GL_POOL_TEMP_BUFFERS_SIZE / 9) { // avoid allocation when uploading few enough uniforms count *= 9; var view = miniTempWebGLFloatBuffers[count]; @@ -2833,7 +2835,7 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; #endif #if GL_POOL_TEMP_BUFFERS - if (count <= {{{ GL_POOL_TEMP_BUFFERS_SIZE / 16 }}}) { + if (count <= GL_POOL_TEMP_BUFFERS_SIZE / 16) { // avoid allocation when uploading few enough uniforms var view = miniTempWebGLFloatBuffers[16*count]; // hoist the heap out of the loop for size and for pthreads+growth. diff --git a/src/modules.mjs b/src/modules.mjs index 3691e964b69c2..a0ab61dbc9930 100644 --- a/src/modules.mjs +++ b/src/modules.mjs @@ -30,6 +30,7 @@ import {preprocess, processMacros} from './parseTools.mjs'; // List of symbols that were added from the library. export const librarySymbols = []; +export const addedLibraryItems = {}; // Library symbols exported via the `__export` decorator. export const extraExports = new Set(); // Map of library symbols which are aliases for native symbols @@ -438,14 +439,12 @@ function isInternalSymbol(ident) { } 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); + if (!(ident in addedLibraryItems)) { + missingSyms.add(ident.slice(1)); } } } diff --git a/src/runtime_common.js b/src/runtime_common.js index 0ed87c326ed4c..3c29b7fae0738 100644 --- a/src/runtime_common.js +++ b/src/runtime_common.js @@ -55,7 +55,7 @@ if (ENVIRONMENT_IS_NODE && {{{ ENVIRONMENT_IS_WORKER_THREAD() }}}) { #if PTHREADS_DEBUG dbg(`uncaughtException on worker thread: ${err.message}`); #endif - postMessage({ cmd: {{{ CMD_UNCAUGHT_EXN }}}, error: err }); + postMessage({ cmd: CMD_UNCAUGHT_EXN, error: err }); // Also shut down the Worker to match the same semantics as if this uncaughtException // handler was not registered. // (n.b. this will not shut down the whole Node.js app process, but just the Worker) diff --git a/src/runtime_pthread.js b/src/runtime_pthread.js index 27996fc16499e..b3bb1ba22fbfa 100644 --- a/src/runtime_pthread.js +++ b/src/runtime_pthread.js @@ -46,7 +46,7 @@ if (ENVIRONMENT_IS_PTHREAD) { var msgData = e.data; //dbg('msgData: ' + Object.keys(msgData)); var cmd = msgData.cmd; - if (cmd == {{{ CMD_LOAD }}}) { // Preload command that is called once per worker to parse and load the Emscripten code. + if (cmd == CMD_LOAD) { // Preload command that is called once per worker to parse and load the Emscripten code. #if ASSERTIONS workerID = msgData.workerID; #endif @@ -61,7 +61,7 @@ if (ENVIRONMENT_IS_PTHREAD) { // And add a callback for when the runtime is initialized. startWorker = () => { // Notify the main thread that this thread has loaded. - postMessage({ cmd: {{{ CMD_LOADED }}} }); + postMessage({ cmd: CMD_LOADED }); // Process any messages that were queued before the thread was ready. for (let msg of messageQueue) { handleMessage(msg); @@ -92,7 +92,7 @@ if (ENVIRONMENT_IS_PTHREAD) { #if RUNTIME_DEBUG dbg(`worker: calling handler on main thread: ${handler}`); #endif - postMessage({ cmd: {{{ CMD_CALL_HANDLER }}}, handler, args: args }); + postMessage({ cmd: CMD_CALL_HANDLER, handler, args: args }); } // Rebind the out / err handlers if needed if (handler == 'print') out = Module[handler]; @@ -130,7 +130,7 @@ if (ENVIRONMENT_IS_PTHREAD) { #if !MINIMAL_RUNTIME startWorker(); #endif - } else if (cmd == {{{ CMD_RUN }}}) { + } else if (cmd == CMD_RUN) { #if ASSERTIONS assert(msgData.pthread_ptr); assert(wasmMemory, "CMD_RUN received before CMD_LOAD"); @@ -177,7 +177,7 @@ if (ENVIRONMENT_IS_PTHREAD) { dbg(`worker: Pthread 0x${_pthread_self().toString(16)} completed its main entry point with an 'unwind', keeping the worker alive for asynchronous operation.`); #endif } - } else if (cmd == {{{ CMD_CHECK_MAILBOX }}}) { + } else if (cmd == CMD_CHECK_MAILBOX) { if (initializedJS) { checkMailbox(); } diff --git a/src/utility.mjs b/src/utility.mjs index c163f82189f79..f3670f7065333 100644 --- a/src/utility.mjs +++ b/src/utility.mjs @@ -186,6 +186,7 @@ export function mergeInto(obj, other, options = null) { __i53abi: 'boolean', __export: 'boolean', __force: 'boolean', + __const: 'boolean', }; const expected = decoratorTypes[decoratorName]; if (type !== expected && !expected.includes(type)) { @@ -223,12 +224,30 @@ export const decoratorSuffixes = [ '__i53abi', '__export', '__force', + '__const', ]; export function isDecorator(ident) { return decoratorSuffixes.some((suffix) => ident.endsWith(suffix)); } +export function defineConsts(consts) { + const rtn = {}; + for (const [key, value] of Object.entries(consts)) { + rtn[key] = value; + rtn[key + '__const'] = true; + } + return rtn; +} + +export function defineInternalConsts(consts) { + const rtn = defineConsts(consts); + for (const key of Object.keys(consts)) { + rtn[key + '__internal'] = true; + } + return rtn; +} + export function readFile(filename) { return fs.readFileSync(filename, 'utf8'); } @@ -385,6 +404,8 @@ export function runInMacroContext(code, options) { addToCompileTimeContext({ assert, decoratorSuffixes, + defineConsts, + defineInternalConsts, error, isDecorator, isJsOnlySymbol, diff --git a/test/codesize/test_codesize_minimal_O0.expected.js b/test/codesize/test_codesize_minimal_O0.expected.js index e82c6c5015078..4e920466dca16 100644 --- a/test/codesize/test_codesize_minimal_O0.expected.js +++ b/test/codesize/test_codesize_minimal_O0.expected.js @@ -798,6 +798,8 @@ Module['FS_createPreloadedFile'] = FS.createPreloadedFile; 'convertI32PairToI53', 'convertI32PairToI53Checked', 'convertU32PairToI53', + 'INT53_MAX', + 'INT53_MIN', 'bigintToI53Checked', 'stackAlloc', 'getTempRet0', @@ -966,8 +968,6 @@ missingLibrarySymbols.forEach(missingLibrarySymbol) 'wasmExports', 'writeStackCookie', 'checkStackCookie', - 'INT53_MAX', - 'INT53_MIN', 'HEAP8', 'HEAPU8', 'HEAP16', diff --git a/test/codesize/test_unoptimized_code_size.json b/test/codesize/test_unoptimized_code_size.json index 49629a20e7232..ccf56fa409a36 100644 --- a/test/codesize/test_unoptimized_code_size.json +++ b/test/codesize/test_unoptimized_code_size.json @@ -8,9 +8,9 @@ "no_asserts.wasm": 11389, "no_asserts.wasm.gz": 5645, "strict.js": 49944, - "strict.js.gz": 15826, + "strict.js.gz": 15827, "strict.wasm": 14275, "strict.wasm.gz": 7096, "total": 164771, - "total_gz": 60319 + "total_gz": 60320 } diff --git a/test/test_jslib.py b/test/test_jslib.py index 759a61209a589..89ded9366bfbb 100644 --- a/test/test_jslib.py +++ b/test/test_jslib.py @@ -1032,3 +1032,77 @@ def test_duplicate_jslib_exports(self, args): js = read_file('main.js') self.assertEqual(js.count("Module['decoratedExport'] = decoratedExport;"), 1) self.assertEqual(js.count("Module['runtimeExport'] = runtimeExport;"), 1) + + @parameterized({ + '': ([],), + 'full_export_all': (['-sINCLUDE_FULL_LIBRARY', '-sEXPORT_ALL'],), + 'O3': (['-O3'],), + 'closure': (['--closure=1'],), + }) + def test_jslib_const(self, args): + create_file('lib.js', r''' + addToLibrary({ + $CONST_A__const: true, + $CONST_A: 10, + ...defineConsts({ + $CONST_B: 22, + $UNUSED_CONST: 99, + }), + ...defineInternalConsts({ + $CONST_C: 10, + $UNUSED_INTERNAL_CONST: 99, + }), + get_const_sum: () => CONST_A + CONST_B + CONST_C, + }); + ''') + create_file('src.c', r''' + #include + #include + int get_const_sum(void); + int main() { + assert(get_const_sum() == 42); + printf("done\n"); + return 0; + } + ''') + self.do_runf('src.c', 'done\n', cflags=['--js-library', 'lib.js'] + args) + js = read_file('src.js') + if '-O3' in args or '--closure=1' in args: + self.assertNotContained('const CONST_A', js) + self.assertNotContained('const CONST_B', js) + self.assertNotContained('const CONST_C', js) + else: + self.assertContained('const CONST_A = 10;', js) + self.assertContained('const CONST_B = 22;', js) + self.assertContained('const CONST_C = 10;', js) + self.assertNotContained('const UNUSED_CONST', js) + self.assertNotContained('UNUSED_INTERNAL_CONST', js) + self.assertNotContained("Module['CONST_A']", js) + + def test_jslib_const_errors(self): + create_file('lib.js', r''' + addToLibrary({ + $bad_func__const: true, + $bad_func: () => 42, + }); + ''') + self.assert_fail([EMCC, test_file('hello_world.c'), '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$bad_func', '--js-library', 'lib.js'], + "error: JS library error: '__const' decorator cannot be used with functions: '$bad_func'") + + create_file('lib.js', r''' + addToLibrary({ + $bad_semi__const: true, + $bad_semi: ';bad_semi = 42;', + }); + ''') + self.assert_fail([EMCC, test_file('hello_world.c'), '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$bad_semi', '--js-library', 'lib.js'], + "error: JS library error: '__const' decorator cannot be used with ';'-prefixed snippets: '$bad_semi'") + + create_file('lib.js', r''' + addToLibrary({ + $bad_undef__const: true, + $bad_undef: undefined, + }); + ''') + self.assert_fail([EMCC, test_file('hello_world.c'), '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$bad_undef', '--js-library', 'lib.js'], + "error: JS library error: '__const' decorator cannot be used with undefined symbols: '$bad_undef'") diff --git a/tools/link.py b/tools/link.py index 9b817534cd5fd..ab35f9d598d56 100644 --- a/tools/link.py +++ b/tools/link.py @@ -485,6 +485,11 @@ def setup_pthreads(): '$PThread', '$establishStackSpace', '$invokeEntryPoint', + '$CMD_LOAD', + '$CMD_LOADED', + '$CMD_CALL_HANDLER', + '$CMD_RUN', + '$CMD_CHECK_MAILBOX', ] if settings.MINIMAL_RUNTIME: @@ -1594,6 +1599,8 @@ def limit_incoming_module_api(): if settings.PTHREADS or settings.WASM_WORKERS: settings.IMPORTED_MEMORY = 1 + if settings.ENVIRONMENT_MAY_BE_NODE and not settings.WASM_ESM_INTEGRATION: + settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$CMD_UNCAUGHT_EXN') set_initial_memory()