diff --git a/cmake/QtWasmHelpers.cmake b/cmake/QtWasmHelpers.cmake index 08e8d97349..c41de308af 100644 --- a/cmake/QtWasmHelpers.cmake +++ b/cmake/QtWasmHelpers.cmake @@ -75,9 +75,13 @@ function (qt_internal_setup_wasm_target_properties wasmTarget) set(QT_CFLAGS_OPTIMIZE_DEBUG "-Os" CACHE STRING INTERNAL FORCE) set(QT_FEATURE_optimize_debug ON CACHE BOOL INTERNAL FORCE) - target_link_options("${wasmTarget}" INTERFACE "SHELL:-s ASYNCIFY" "-Os" "-s" "ASYNCIFY_IMPORTS=[qt_asyncify_suspend_js, qt_asyncify_resume_js]") + target_link_options("${wasmTarget}" INTERFACE "SHELL:-s ASYNCIFY" "-Os") target_compile_definitions("${wasmTarget}" INTERFACE QT_HAVE_EMSCRIPTEN_ASYNCIFY) endif() + + # Set ASYNCIFY_IMPORTS unconditionally in order to support enabling asyncify at link time. + target_link_options("${wasmTarget}" INTERFACE "SHELL:-sASYNCIFY_IMPORTS=qt_asyncify_suspend_js,qt_asyncify_resume_js") + endfunction() function(qt_internal_wasm_add_finalizers target) diff --git a/mkspecs/wasm-emscripten/qmake.conf b/mkspecs/wasm-emscripten/qmake.conf index 648246b268..6bb99d49b6 100644 --- a/mkspecs/wasm-emscripten/qmake.conf +++ b/mkspecs/wasm-emscripten/qmake.conf @@ -22,12 +22,12 @@ load(emcc_ver) # (with "wasm validation error: too many locals" type errors) if optimizations # are omitted. Enable optimizations also for debug builds. QMAKE_LFLAGS_DEBUG += -Os - - # Declare all async functions - QMAKE_LFLAGS += -s \'ASYNCIFY_IMPORTS=[\"qt_asyncify_suspend_js\", \"qt_asyncify_resume_js\", \"emscripten_sleep\"]\' } } +# Declare async functions +QMAKE_LFLAGS += -s \'ASYNCIFY_IMPORTS=qt_asyncify_suspend_js,qt_asyncify_resume_js\' + EMCC_COMMON_LFLAGS += \ -s WASM=1 \ -s MAX_WEBGL_VERSION=2 \ diff --git a/src/corelib/kernel/qeventdispatcher_wasm.cpp b/src/corelib/kernel/qeventdispatcher_wasm.cpp index 2363d5a79b..82c960cebb 100644 --- a/src/corelib/kernel/qeventdispatcher_wasm.cpp +++ b/src/corelib/kernel/qeventdispatcher_wasm.cpp @@ -26,14 +26,16 @@ Q_LOGGING_CATEGORY(lcEventDispatcherTimers, "qt.eventdispatcher.timers"); #define LOCK_GUARD(M) #endif -#ifdef QT_HAVE_EMSCRIPTEN_ASYNCIFY - // Emscripten asyncify currently supports one level of suspend - // recursion is not permitted. We track the suspend state here // on order to fail (more) gracefully, but we can of course only // track Qts own usage of asyncify. static bool g_is_asyncify_suspended = false; +EM_JS(bool, qt_have_asyncify_js, (), { + return typeof Asyncify != "undefined"; +}); + EM_JS(void, qt_asyncify_suspend_js, (), { let sleepFn = (wakeUp) => { Module.qtAsyncifyWakeUp = wakeUp; @@ -52,6 +54,15 @@ EM_JS(void, qt_asyncify_resume_js, (), { setTimeout(wakeUp); }); +// Returns true if asyncify is available. +bool qt_have_asyncify() +{ + static bool have_asyncify = []{ + return qt_have_asyncify_js(); + }(); + return have_asyncify; +} + // Suspends the main thread until qt_asyncify_resume() is called. Returns // false immediately if Qt has already suspended the main thread (recursive // suspend is not supported by Emscripten). Returns true (after resuming), @@ -76,8 +87,6 @@ bool qt_asyncify_resume() return true; } -#endif // QT_HAVE_EMSCRIPTEN_ASYNCIFY - Q_CONSTINIT QEventDispatcherWasm *QEventDispatcherWasm::g_mainThreadEventDispatcher = nullptr; #if QT_CONFIG(thread) Q_CONSTINIT QVector QEventDispatcherWasm::g_secondaryThreadEventDispatchers; @@ -359,16 +368,15 @@ void QEventDispatcherWasm::handleApplicationExec() void QEventDispatcherWasm::handleDialogExec() { -#ifndef QT_HAVE_EMSCRIPTEN_ASYNCIFY - qWarning() << "Warning: dialog exec() is not supported on Qt for WebAssembly in this" - << "configuration. Please use show() instead, or enable experimental support" - << "for asyncify.\n" - << "When using exec() (without asyncify) the dialog will show, the user can interact" - << "with it and the appropriate signals will be emitted on close. However, the" - << "exec() call never returns, stack content at the time of the exec() call" - << "is leaked, and the exec() call may interfere with input event processing"; - emscripten_sleep(1); // This call never returns -#endif + if (!qt_have_asyncify()) { + qWarning() << "Warning: dialog exec() is not supported on Qt for WebAssembly in this" + << "configuration. Please use show() instead, or enable experimental support" + << "for asyncify.\n" + << "When using exec() (without asyncify) the dialog will show, the user can interact" + << "exec() call never returns, stack content at the time of the exec() call" + << "is leaked, and the exec() call may interfere with input event processing"; + emscripten_sleep(1); // This call never returns + } // For the asyncify case we do nothing here and wait for events in wait() } @@ -393,20 +401,20 @@ bool QEventDispatcherWasm::wait(int timeout) #endif Q_ASSERT(emscripten_is_main_runtime_thread()); Q_ASSERT(isMainThreadEventDispatcher()); -#ifdef QT_HAVE_EMSCRIPTEN_ASYNCIFY - if (timeout > 0) - qWarning() << "QEventDispatcherWasm asyncify wait with timeout is not supported; timeout will be ignored"; // FIXME + if (qt_have_asyncify()) { + if (timeout > 0) + qWarning() << "QEventDispatcherWasm asyncify wait with timeout is not supported; timeout will be ignored"; // FIXME - bool didSuspend = qt_asyncify_suspend(); - if (!didSuspend) { - qWarning("QEventDispatcherWasm: current thread is already suspended; could not asyncify wait for events"); - return false; + bool didSuspend = qt_asyncify_suspend(); + if (!didSuspend) { + qWarning("QEventDispatcherWasm: current thread is already suspended; could not asyncify wait for events"); + return false; + } + return true; + } else { + qWarning("QEventLoop::WaitForMoreEvents is not supported on the main thread without asyncify"); + Q_UNUSED(timeout); } - return true; -#else - qWarning("QEventLoop::WaitForMoreEvents is not supported on the main thread without asyncify"); - Q_UNUSED(timeout); -#endif return false; } @@ -424,12 +432,10 @@ bool QEventDispatcherWasm::wakeEventDispatcherThread() } #endif Q_ASSERT(isMainThreadEventDispatcher()); -#ifdef QT_HAVE_EMSCRIPTEN_ASYNCIFY if (g_is_asyncify_suspended) { runOnMainThread([]{ qt_asyncify_resume(); }); return true; } -#endif return false; } diff --git a/tests/manual/wasm/eventloop/asyncify_exec/CMakeLists.txt b/tests/manual/wasm/eventloop/asyncify_exec/CMakeLists.txt index f8328660b2..28825e38a0 100644 --- a/tests/manual/wasm/eventloop/asyncify_exec/CMakeLists.txt +++ b/tests/manual/wasm/eventloop/asyncify_exec/CMakeLists.txt @@ -7,3 +7,6 @@ qt_internal_add_manual_test(asyncify_exec LIBRARIES Qt::Core ) + +# Enable asyncify for this test. Also enable optimizations in order to reduce the binary size. +target_link_options(asyncify_exec PUBLIC -sASYNCIFY -Os) diff --git a/tests/manual/wasm/eventloop/asyncify_exec/main.cpp b/tests/manual/wasm/eventloop/asyncify_exec/main.cpp index c3a827ac11..ab3018c12e 100644 --- a/tests/manual/wasm/eventloop/asyncify_exec/main.cpp +++ b/tests/manual/wasm/eventloop/asyncify_exec/main.cpp @@ -2,14 +2,12 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include -// This test shows how to asyncify enables blocking -// the main thread on QEventLoop::exec(), while event -// provessing continues. +// This test shows how to use asyncify to enable blocking the main +// thread on QEventLoop::exec(), while event processing continues. int main(int argc, char **argv) { QCoreApplication app(argc, argv); -#ifdef QT_HAVE_EMSCRIPTEN_ASYNCIFY QTimer::singleShot(1000, []() { QEventLoop loop; @@ -22,10 +20,6 @@ int main(int argc, char **argv) loop.exec(); qDebug() << "Returned from QEventLoop::exec()"; }); -#else - qDebug() << "This test requires Emscripten asyncify. To enable," - << "configure Qt with -device-option QT_EMSCRIPTEN_ASYNCIFY=1"; -#endif app.exec(); }