wasm: include asyncify support unconditionally

Emscripten's option for enabling asyncify (-sASYNCIFY) is a link-time
option, which means there is no requirement to have a separate asyncify
build, at least for static builds.

Replace the current QT_HAVE_EMSCRIPTEN_ASYNCIFY compile-time option
with a run-time option which checks if the asyncify API is available.

Keep support for configuring with "-device-option QT_EMSCRIPTEN_ASYNCIFY=1"
for backwards compatibility and for the use case where want asyncify
support to be on by default for a given Qt build.

Enable asyncify for the asyncify_exec example.

Pick-to: 6.4
Change-Id: I301fd7e2d3c0367532c886f4e34b23e1093646ad
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
Morten Sørvig 2022-08-09 00:05:33 +02:00
parent 238e90cd58
commit f347682fd5
5 changed files with 47 additions and 40 deletions

View File

@ -75,9 +75,13 @@ function (qt_internal_setup_wasm_target_properties wasmTarget)
set(QT_CFLAGS_OPTIMIZE_DEBUG "-Os" CACHE STRING INTERNAL FORCE) set(QT_CFLAGS_OPTIMIZE_DEBUG "-Os" CACHE STRING INTERNAL FORCE)
set(QT_FEATURE_optimize_debug ON CACHE BOOL 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) target_compile_definitions("${wasmTarget}" INTERFACE QT_HAVE_EMSCRIPTEN_ASYNCIFY)
endif() 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() endfunction()
function(qt_internal_wasm_add_finalizers target) function(qt_internal_wasm_add_finalizers target)

View File

@ -22,11 +22,11 @@ load(emcc_ver)
# (with "wasm validation error: too many locals" type errors) if optimizations # (with "wasm validation error: too many locals" type errors) if optimizations
# are omitted. Enable optimizations also for debug builds. # are omitted. Enable optimizations also for debug builds.
QMAKE_LFLAGS_DEBUG += -Os QMAKE_LFLAGS_DEBUG += -Os
}
}
# Declare all async functions # Declare async functions
QMAKE_LFLAGS += -s \'ASYNCIFY_IMPORTS=[\"qt_asyncify_suspend_js\", \"qt_asyncify_resume_js\", \"emscripten_sleep\"]\' QMAKE_LFLAGS += -s \'ASYNCIFY_IMPORTS=qt_asyncify_suspend_js,qt_asyncify_resume_js\'
}
}
EMCC_COMMON_LFLAGS += \ EMCC_COMMON_LFLAGS += \
-s WASM=1 \ -s WASM=1 \

View File

@ -26,14 +26,16 @@ Q_LOGGING_CATEGORY(lcEventDispatcherTimers, "qt.eventdispatcher.timers");
#define LOCK_GUARD(M) #define LOCK_GUARD(M)
#endif #endif
#ifdef QT_HAVE_EMSCRIPTEN_ASYNCIFY
// Emscripten asyncify currently supports one level of suspend - // Emscripten asyncify currently supports one level of suspend -
// recursion is not permitted. We track the suspend state here // recursion is not permitted. We track the suspend state here
// on order to fail (more) gracefully, but we can of course only // on order to fail (more) gracefully, but we can of course only
// track Qts own usage of asyncify. // track Qts own usage of asyncify.
static bool g_is_asyncify_suspended = false; 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, (), { EM_JS(void, qt_asyncify_suspend_js, (), {
let sleepFn = (wakeUp) => { let sleepFn = (wakeUp) => {
Module.qtAsyncifyWakeUp = wakeUp; Module.qtAsyncifyWakeUp = wakeUp;
@ -52,6 +54,15 @@ EM_JS(void, qt_asyncify_resume_js, (), {
setTimeout(wakeUp); 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 // Suspends the main thread until qt_asyncify_resume() is called. Returns
// false immediately if Qt has already suspended the main thread (recursive // false immediately if Qt has already suspended the main thread (recursive
// suspend is not supported by Emscripten). Returns true (after resuming), // suspend is not supported by Emscripten). Returns true (after resuming),
@ -76,8 +87,6 @@ bool qt_asyncify_resume()
return true; return true;
} }
#endif // QT_HAVE_EMSCRIPTEN_ASYNCIFY
Q_CONSTINIT QEventDispatcherWasm *QEventDispatcherWasm::g_mainThreadEventDispatcher = nullptr; Q_CONSTINIT QEventDispatcherWasm *QEventDispatcherWasm::g_mainThreadEventDispatcher = nullptr;
#if QT_CONFIG(thread) #if QT_CONFIG(thread)
Q_CONSTINIT QVector<QEventDispatcherWasm *> QEventDispatcherWasm::g_secondaryThreadEventDispatchers; Q_CONSTINIT QVector<QEventDispatcherWasm *> QEventDispatcherWasm::g_secondaryThreadEventDispatchers;
@ -359,16 +368,15 @@ void QEventDispatcherWasm::handleApplicationExec()
void QEventDispatcherWasm::handleDialogExec() void QEventDispatcherWasm::handleDialogExec()
{ {
#ifndef QT_HAVE_EMSCRIPTEN_ASYNCIFY if (!qt_have_asyncify()) {
qWarning() << "Warning: dialog exec() is not supported on Qt for WebAssembly in this" qWarning() << "Warning: dialog exec() is not supported on Qt for WebAssembly in this"
<< "configuration. Please use show() instead, or enable experimental support" << "configuration. Please use show() instead, or enable experimental support"
<< "for asyncify.\n" << "for asyncify.\n"
<< "When using exec() (without asyncify) the dialog will show, the user can interact" << "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" << "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"; << "is leaked, and the exec() call may interfere with input event processing";
emscripten_sleep(1); // This call never returns emscripten_sleep(1); // This call never returns
#endif }
// For the asyncify case we do nothing here and wait for events in wait() // For the asyncify case we do nothing here and wait for events in wait()
} }
@ -393,7 +401,7 @@ bool QEventDispatcherWasm::wait(int timeout)
#endif #endif
Q_ASSERT(emscripten_is_main_runtime_thread()); Q_ASSERT(emscripten_is_main_runtime_thread());
Q_ASSERT(isMainThreadEventDispatcher()); Q_ASSERT(isMainThreadEventDispatcher());
#ifdef QT_HAVE_EMSCRIPTEN_ASYNCIFY if (qt_have_asyncify()) {
if (timeout > 0) if (timeout > 0)
qWarning() << "QEventDispatcherWasm asyncify wait with timeout is not supported; timeout will be ignored"; // FIXME qWarning() << "QEventDispatcherWasm asyncify wait with timeout is not supported; timeout will be ignored"; // FIXME
@ -403,10 +411,10 @@ bool QEventDispatcherWasm::wait(int timeout)
return false; return false;
} }
return true; return true;
#else } else {
qWarning("QEventLoop::WaitForMoreEvents is not supported on the main thread without asyncify"); qWarning("QEventLoop::WaitForMoreEvents is not supported on the main thread without asyncify");
Q_UNUSED(timeout); Q_UNUSED(timeout);
#endif }
return false; return false;
} }
@ -424,12 +432,10 @@ bool QEventDispatcherWasm::wakeEventDispatcherThread()
} }
#endif #endif
Q_ASSERT(isMainThreadEventDispatcher()); Q_ASSERT(isMainThreadEventDispatcher());
#ifdef QT_HAVE_EMSCRIPTEN_ASYNCIFY
if (g_is_asyncify_suspended) { if (g_is_asyncify_suspended) {
runOnMainThread([]{ qt_asyncify_resume(); }); runOnMainThread([]{ qt_asyncify_resume(); });
return true; return true;
} }
#endif
return false; return false;
} }

View File

@ -7,3 +7,6 @@ qt_internal_add_manual_test(asyncify_exec
LIBRARIES LIBRARIES
Qt::Core 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)

View File

@ -2,14 +2,12 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtCore> #include <QtCore>
// This test shows how to asyncify enables blocking // This test shows how to use asyncify to enable blocking the main
// the main thread on QEventLoop::exec(), while event // thread on QEventLoop::exec(), while event processing continues.
// provessing continues.
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
QCoreApplication app(argc, argv); QCoreApplication app(argc, argv);
#ifdef QT_HAVE_EMSCRIPTEN_ASYNCIFY
QTimer::singleShot(1000, []() { QTimer::singleShot(1000, []() {
QEventLoop loop; QEventLoop loop;
@ -22,10 +20,6 @@ int main(int argc, char **argv)
loop.exec(); loop.exec();
qDebug() << "Returned from QEventLoop::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(); app.exec();
} }