diff --git a/configure.ac b/configure.ac index ae1320b..95e5b5d 100644 --- a/configure.ac +++ b/configure.ac @@ -26,7 +26,13 @@ OPT_LEVEL=3 if test "$GCC" = "yes" then WARNINGS="-Wall -Wdeclaration-after-statement -Wredundant-decls" - CFLAGS="-O$OPT_LEVEL -g $WARNINGS $orig_CFLAGS" + # -std=gnu99: modern clang defaults to C23, where true/false are + # keywords and collide with the boolean enum in doomtype.h. + # -fno-strict-aliasing: the id-era code type-puns byte buffers through + # int16/int32 pointers everywhere (e.g. wipe_shittyColMajorXform); at + # -O3 modern LLVM exploits that UB and miscompiles the screen-wipe + # into a transposed, never-finishing mess. + CFLAGS="-O$OPT_LEVEL -std=gnu99 -fno-strict-aliasing $WARNINGS $orig_CFLAGS" fi # PKG_CHECK_MODULES(SDL, [sdl2 >= 2.0.7]) @@ -115,10 +121,19 @@ AS_IF([test "x$with_libpng" != xno], [ # TODO: We currently link everything against libraries that don't need it. # Use the specific library CFLAGS/LIBS variables instead of setting them here. -EMFLAGS="-gsource-map -s INVOKE_RUN=1 -s USE_SDL=2 -s USE_SDL_MIXER=2 -s LEGACY_GL_EMULATION=0 -s USE_SDL_NET=2 -s ASSERTIONS=0 -s WASM=1 -s ALLOW_MEMORY_GROWTH=0 -s FORCE_FILESYSTEM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS=[['FS','ccall']] -s SAFE_HEAP=1 -s EXIT_RUNTIME=1 -s STACK_OVERFLOW_CHECK=1 -s PROXY_POSIX_SOCKETS=0 -s USE_PTHREADS=0 -s PROXY_TO_PTHREAD=0 -s TOTAL_MEMORY=64MB -s ERROR_ON_UNDEFINED_SYMBOLS=0 -s ASYNCIFY -O3 --source-map-base /" +# Release flags: no SAFE_HEAP/STACK_OVERFLOW_CHECK/source maps (debug-only, large +# runtime cost), closure for smaller JS glue. ASYNCIFY is still required for +# emscripten_sleep() on startup and netgame wait paths (see I_Sleep). +# Do NOT add -flto: cross-TU LTO at -O3 turns undefined behavior in this +# 1993-era codebase into a compile-time `unreachable` trap in main() (verified: +# the LTO build crashes immediately after Z_Init). +# STACK_SIZE=5MB restores the pre-3.1.25 Emscripten default; the modern 64KB +# default is too small for Doom's init path. +EMFLAGS="-s INVOKE_RUN=1 -s USE_SDL=2 -s USE_SDL_MIXER=2 -s LEGACY_GL_EMULATION=0 -s USE_SDL_NET=2 -s ASSERTIONS=0 -s WASM=1 -s ALLOW_MEMORY_GROWTH=0 -s FORCE_FILESYSTEM=1 -s EXPORTED_RUNTIME_METHODS=[['FS','ccall']] -s EXIT_RUNTIME=1 -s PROXY_POSIX_SOCKETS=0 -s USE_PTHREADS=0 -s PROXY_TO_PTHREAD=0 -s INITIAL_MEMORY=64MB -s STACK_SIZE=5MB -s ERROR_ON_UNDEFINED_SYMBOLS=0 -s ASYNCIFY -O3" +EMLDFLAGS="--closure 1" CFLAGS="$CFLAGS $SDL_CFLAGS ${SAMPLERATE_CFLAGS:-} ${PNG_CFLAGS:-} $EMFLAGS" -LDFLAGS="$LDFLAGS $SDL_LIBS ${SAMPLERATE_LIBS:-} ${PNG_LIBS:-} $EMFLAGS -lwebsocket.js" +LDFLAGS="$LDFLAGS $SDL_LIBS ${SAMPLERATE_LIBS:-} ${PNG_LIBS:-} $EMFLAGS $EMLDFLAGS -lwebsocket.js" AC_CHECK_LIB(m, log) diff --git a/src/d_loop.c b/src/d_loop.c index e5f3a3c..5b851c7 100644 --- a/src/d_loop.c +++ b/src/d_loop.c @@ -661,16 +661,15 @@ void TryRunTics(void) if (lowtic < gametic / ticdup) I_Error("TryRunTics: lowtic < gametic"); - // Still no tics to run? Sleep until some are available. + // Still no tics to run? Return to the browser event loop instead + // of sleeping: I_Sleep() is emscripten_sleep(), which Asyncify + // implements by unwinding and rewinding the entire call stack. + // This loop is reached on almost every frame (the 60Hz+ rAF main + // loop outpaces the 35Hz tic rate), so sleeping here put that + // unwind cost on the hot path. The main loop calls TryRunTics() + // again on the next frame, which serves the same purpose. if (lowtic < gametic / ticdup + counts) { - // If we're in a netgame, we might spin forever waiting for - // new network data to be received. So don't stay in here - // forever - give the menu a chance to work. - if (I_GetTime() / ticdup - entertic >= MAX_NETGAME_STALL_TICS) { - return; - } - - I_Sleep(1); + return; } } diff --git a/src/doom/d_main.c b/src/doom/d_main.c index 9a462e2..eea03ff 100644 --- a/src/doom/d_main.c +++ b/src/doom/d_main.c @@ -375,11 +375,14 @@ void D_RunFrame() static boolean wipe; if (wipe) { - do { - nowtime = I_GetTime(); - tics = nowtime - wipestart; - I_Sleep(1); - } while (tics <= 0); + // No game tic has elapsed yet: return to the browser event loop + // rather than Asyncify-sleeping (stack unwind/rewind) inside the + // frame. The main loop re-enters D_RunFrame on the next rAF frame. + nowtime = I_GetTime(); + tics = nowtime - wipestart; + if (tics <= 0) { + return; + } wipestart = nowtime; wipe = !wipe_ScreenWipe(wipe_Melt, 0, 0, SCREENWIDTH, SCREENHEIGHT, tics); diff --git a/src/doom/r_defs.h b/src/doom/r_defs.h index d87ca38..ff518b4 100644 --- a/src/doom/r_defs.h +++ b/src/doom/r_defs.h @@ -392,7 +392,10 @@ typedef struct // If false use 0 for any position. // Note: as eight entries are available, // we might as well insert the same name eight times. - boolean rotate; + // int, not boolean: R_InstallSpriteLump memsets this to -1 as an + // "uninitialized" marker and compares against it; a 1-byte bool + // cannot hold -1. + int rotate; // Lump to use for view angles 0-7. short lump[8]; diff --git a/src/doomtype.h b/src/doomtype.h index 9947a07..9d6eb88 100644 --- a/src/doomtype.h +++ b/src/doomtype.h @@ -100,22 +100,15 @@ #include -#if defined(__cplusplus) || defined(__bool_true_false_are_defined) - -// Use builtin bool type with C++. +// boolean must be the same size in every translation unit. The old +// conditional (enum when hadn't been included yet, bool when it +// had) made boolean 4 bytes in some TUs and 1 byte in others, so 4-byte +// stores to shared globals like netgame clobbered adjacent globals +// (caught by ASan as a global-buffer-overflow). Always use bool. +#include typedef bool boolean; -#else - -typedef enum -{ - false, - true -} boolean; - -#endif - typedef uint8_t byte; typedef uint8_t pixel_t; typedef int16_t dpixel_t; diff --git a/src/index.html b/src/index.html index 114d9e4..a1621f1 100644 --- a/src/index.html +++ b/src/index.html @@ -56,14 +56,25 @@