Make WASM export names different across modules

The export name is now ${TARGET_NAME}Entry. This can also be overridden
by using QT_WASM_EXPORT_NAME, both in CMake and qmake

Change-Id: I59c97ae6e22f0b2720716e9d7eff7b6b13d37ab5
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
Mikolaj Boc 2022-12-29 17:54:29 +01:00
parent db9e206dee
commit 1f6cac0da9
18 changed files with 82 additions and 46 deletions

View File

@ -18,14 +18,9 @@ function (qt_internal_setup_wasm_target_properties wasmTarget)
target_compile_options("${wasmTarget}" INTERFACE "SHELL:-s MEMORY64=1" ) target_compile_options("${wasmTarget}" INTERFACE "SHELL:-s MEMORY64=1" )
target_link_options("${wasmTarget}" INTERFACE "SHELL:-s MEMORY64=1" -mwasm64) target_link_options("${wasmTarget}" INTERFACE "SHELL:-s MEMORY64=1" -mwasm64)
endif() endif()
# Enable MODULARIZE and set EXPORT_NAME, which makes it possible to # Enable MODULARIZE so that we are able to set EXPORT_NAME later and instantiate on demand (with
# create application instances using a global constructor function, # MODULARIZE=0, emscripten creates a global app instance object at Javascript eval time)
# e.g. let app_instance = await createQtAppInstance(). target_link_options("${wasmTarget}" INTERFACE "SHELL:-s MODULARIZE=1")
# (as opposed to MODULARIZE=0, where Emscripten creates a global app
# instance object at Javascript eval time)
target_link_options("${wasmTarget}" INTERFACE
"SHELL:-s MODULARIZE=1"
"SHELL:-s EXPORT_NAME=createQtAppInstance")
#simd #simd
if (QT_FEATURE_wasm_simd128) if (QT_FEATURE_wasm_simd128)
@ -126,6 +121,7 @@ function (qt_internal_setup_wasm_target_properties wasmTarget)
endfunction() endfunction()
function(qt_internal_wasm_add_finalizers target) function(qt_internal_wasm_add_finalizers target)
qt_add_list_file_finalizer(_qt_internal_set_wasm_export_name ${target})
qt_add_list_file_finalizer(_qt_internal_add_wasm_extra_exported_methods ${target}) qt_add_list_file_finalizer(_qt_internal_add_wasm_extra_exported_methods ${target})
qt_add_list_file_finalizer(_qt_internal_wasm_add_target_helpers ${target}) qt_add_list_file_finalizer(_qt_internal_wasm_add_target_helpers ${target})
endfunction() endfunction()

View File

@ -35,7 +35,6 @@ EMCC_COMMON_LFLAGS += \
--bind \ --bind \
-s FETCH=1 \ -s FETCH=1 \
-s MODULARIZE=1 \ -s MODULARIZE=1 \
-s EXPORT_NAME=createQtAppInstance \
-s WASM_BIGINT=1 \ -s WASM_BIGINT=1 \
-s STACK_SIZE=5MB -s STACK_SIZE=5MB

View File

@ -15,6 +15,13 @@ exists($$QMAKE_QT_CONFIG) {
} }
EMCC_LFLAGS += -s EXPORTED_RUNTIME_METHODS=$$EXPORTED_METHODS EMCC_LFLAGS += -s EXPORTED_RUNTIME_METHODS=$$EXPORTED_METHODS
!isEmpty(QT_WASM_EXPORT_NAME): {
EXPORT_NAME = $$QT_WASM_EXPORT_NAME
} else {
EXPORT_NAME = $${TARGET}_entry
}
EMCC_LFLAGS += -s EXPORT_NAME=$$EXPORT_NAME
qtConfig(thread) { qtConfig(thread) {
EMCC_LFLAGS += -pthread EMCC_LFLAGS += -pthread

View File

@ -656,6 +656,7 @@ function(_qt_internal_finalize_executable target)
if(EMSCRIPTEN) if(EMSCRIPTEN)
_qt_internal_wasm_add_target_helpers("${target}") _qt_internal_wasm_add_target_helpers("${target}")
_qt_internal_add_wasm_extra_exported_methods("${target}") _qt_internal_add_wasm_extra_exported_methods("${target}")
_qt_internal_set_wasm_export_name("${target}")
endif() endif()
if(IOS) if(IOS)
_qt_internal_finalize_ios_app("${target}") _qt_internal_finalize_ios_app("${target}")

View File

@ -104,3 +104,12 @@ function(_qt_internal_add_wasm_extra_exported_methods target)
) )
endif() endif()
endfunction() endfunction()
function(_qt_internal_set_wasm_export_name target)
get_target_property(export_name "${target}" QT_WASM_EXPORT_NAME)
if(export_name)
target_link_options("${target}" PRIVATE "SHELL:-s EXPORT_NAME=${export_name}")
else()
target_link_options("${target}" PRIVATE "SHELL:-s EXPORT_NAME=${target}_entry")
endif()
endfunction()

View File

@ -49,7 +49,7 @@ async function qtLoad(config)
if (typeof config.qt !== 'object') if (typeof config.qt !== 'object')
throw new Error('config.qt is required, expected an object'); throw new Error('config.qt is required, expected an object');
if (typeof config.qt.entryFunction !== 'function') if (typeof config.qt.entryFunction !== 'function')
config.qt.entryFunction = window.createQtAppInstance; throw new Error('config.qt.entryFunction is required, expected a function');
config.qtContainerElements = config.qt.containerElements; config.qtContainerElements = config.qt.containerElements;
delete config.qt.containerElements; delete config.qt.containerElements;

View File

@ -57,7 +57,7 @@
exitData.text !== undefined ? ` (${exitData.text})` : ''; exitData.text !== undefined ? ` (${exitData.text})` : '';
showUi(spinner); showUi(spinner);
}, },
entryFunction: window.createQtAppInstance, entryFunction: window.@APPNAME@_entry,
containerElements: [screen], containerElements: [screen],
} }
}); });

View File

@ -7,7 +7,7 @@
<script> <script>
window.onload = async () => { window.onload = async () => {
let qt_instance = await createQtAppInstance({ let qt_instance = await a11y_basic_widgets_entry({
qtContainerElements: [document.getElementById("qt_container")], qtContainerElements: [document.getElementById("qt_container")],
}); });
} }

View File

@ -3,7 +3,7 @@
<script type="text/javascript" src="eventloop_auto.js"></script> <script type="text/javascript" src="eventloop_auto.js"></script>
<script> <script>
window.onload = () => { window.onload = () => {
runTestCase(document.getElementById("log")); runTestCase(eventloop_auto_entry, document.getElementById("log"));
}; };
</script> </script>
<p>Running event dispatcher auto test.</p> <p>Running event dispatcher auto test.</p>

View File

@ -3,7 +3,7 @@
<script type="text/javascript" src="eventloop_auto_asyncify.js"></script> <script type="text/javascript" src="eventloop_auto_asyncify.js"></script>
<script> <script>
window.onload = () => { window.onload = () => {
runTestCase(document.getElementById("log")); runTestCase(eventloop_auto_asyncify_entry, document.getElementById("log"));
}; };
</script> </script>
<p>Running event dispatcher auto test.</p> <p>Running event dispatcher auto test.</p>

View File

@ -3,7 +3,7 @@
<script type="text/javascript" src="sockify_sockets_auto.js"></script> <script type="text/javascript" src="sockify_sockets_auto.js"></script>
<script> <script>
window.onload = async () => { window.onload = async () => {
runTestCase(document.getElementById("log")); runTestCase(sockify_sockets_auto_entry, document.getElementById("log"));
}; };
</script> </script>
<p> Sockify tunneled sockets auto test. <p> Sockify tunneled sockets auto test.

View File

@ -6,7 +6,7 @@
<script type="text/javascript" src="files_auto.js"></script> <script type="text/javascript" src="files_auto.js"></script>
<script> <script>
window.onload = () => { window.onload = () => {
runTestCase(document.getElementById("log")); runTestCase(files_auto_entry, document.getElementById("log"));
}; };
</script> </script>
<p>Running files auto test.</p> <p>Running files auto test.</p>

View File

@ -3,7 +3,7 @@
<script type="text/javascript" src="promise_auto.js"></script> <script type="text/javascript" src="promise_auto.js"></script>
<script> <script>
window.onload = () => { window.onload = () => {
runTestCase(document.getElementById("log")); runTestCase(promise_auto_entry, document.getElementById("log"));
}; };
</script> </script>
<p>Running promise auto test.</p> <p>Running promise auto test.</p>

View File

@ -3,7 +3,7 @@
<script type="text/javascript" src="qwasmcompositor_auto.js"></script> <script type="text/javascript" src="qwasmcompositor_auto.js"></script>
<script> <script>
window.onload = () => { window.onload = () => {
runTestCase(document.getElementById("log")); runTestCase(qwasmcompositor_auto_entry, document.getElementById("log"));
}; };
</script> </script>
<p>Running files auto test.</p> <p>Running files auto test.</p>

View File

@ -48,10 +48,32 @@ export class QtLoaderIntegrationTests
'config.qt is required, expected an object', caughtException.message); 'config.qt is required, expected an object', caughtException.message);
} }
async useDefaultOnMissingEntryFunction() async missingEntryFunction()
{ {
const instance = await qtLoad({ arguments: ['--no-gui'], qt: {}}); let caughtException;
assert.isNotUndefined(instance); try {
await qtLoad({ qt: {}});
} catch (e) {
caughtException = e;
}
assert.isNotUndefined(caughtException);
assert.equal(
'config.qt.entryFunction is required, expected a function', caughtException.message);
}
async badEntryFunction()
{
let caughtException;
try {
await qtLoad({ qt: { entryFunction: 'invalid' }});
} catch (e) {
caughtException = e;
}
assert.isNotUndefined(caughtException);
assert.equal(
'config.qt.entryFunction is required, expected a function', caughtException.message);
} }
async environmentVariables() async environmentVariables()
@ -62,7 +84,7 @@ export class QtLoaderIntegrationTests
variable1: 'value1', variable1: 'value1',
variable2: 'value2' variable2: 'value2'
}, },
entryFunction: createQtAppInstance, entryFunction: tst_qtloader_integration_entry,
containerElements: [this.#testScreenContainers[0]] containerElements: [this.#testScreenContainers[0]]
} }
}); });
@ -79,7 +101,7 @@ export class QtLoaderIntegrationTests
const instance = await qtLoad({ const instance = await qtLoad({
qt: { qt: {
entryFunction: createQtAppInstance, entryFunction: tst_qtloader_integration_entry,
containerElements: this.#testScreenContainers containerElements: this.#testScreenContainers
} }
}); });
@ -125,7 +147,7 @@ export class QtLoaderIntegrationTests
{ {
const instance = await qtLoad({ const instance = await qtLoad({
qt: { qt: {
entryFunction: createQtAppInstance, entryFunction: tst_qtloader_integration_entry,
containerElements: this.#testScreenContainers, containerElements: this.#testScreenContainers,
} }
}); });
@ -181,7 +203,7 @@ export class QtLoaderIntegrationTests
const instances = await Promise.all([1, 2, 3].map(i => qtLoad({ const instances = await Promise.all([1, 2, 3].map(i => qtLoad({
qt: { qt: {
entryFunction: createQtAppInstance, entryFunction: tst_qtloader_integration_entry,
containerElements: [this.#addScreenContainer(`screen-container-${i}`, { containerElements: [this.#addScreenContainer(`screen-container-${i}`, {
width: `${i * 10}px`, width: `${i * 10}px`,
height: `${i * 10}px`, height: `${i * 10}px`,
@ -222,7 +244,7 @@ export class QtLoaderIntegrationTests
accumulatedStdout += output; accumulatedStdout += output;
}, },
qt: { qt: {
entryFunction: createQtAppInstance, entryFunction: tst_qtloader_integration_entry,
} }
}); });
@ -246,7 +268,7 @@ export class QtLoaderIntegrationTests
{ {
await qtLoad({ await qtLoad({
qt: { qt: {
entryFunction: createQtAppInstance, entryFunction: tst_qtloader_integration_entry,
containerElements: [this.#testScreenContainers[0]], containerElements: [this.#testScreenContainers[0]],
module: await WebAssembly.compileStreaming( module: await WebAssembly.compileStreaming(
fetch('tst_qtloader_integration.wasm')) fetch('tst_qtloader_integration.wasm'))
@ -259,7 +281,7 @@ export class QtLoaderIntegrationTests
const instance = await qtLoad({ const instance = await qtLoad({
arguments: ['--no-gui', 'arg1', 'other', 'yetanotherarg'], arguments: ['--no-gui', 'arg1', 'other', 'yetanotherarg'],
qt: { qt: {
entryFunction: createQtAppInstance, entryFunction: tst_qtloader_integration_entry,
} }
}); });
const args = this.#callTestInstanceApi(instance, 'retrieveArguments'); const args = this.#callTestInstanceApi(instance, 'retrieveArguments');
@ -275,7 +297,7 @@ export class QtLoaderIntegrationTests
try { try {
await qtLoad({ await qtLoad({
qt: { qt: {
entryFunction: createQtAppInstance, entryFunction: tst_qtloader_integration_entry,
containerElements: [this.#testScreenContainers[0]], containerElements: [this.#testScreenContainers[0]],
module: Promise.reject(new Error('Failed to load')), module: Promise.reject(new Error('Failed to load')),
} }
@ -294,7 +316,7 @@ export class QtLoaderIntegrationTests
arguments: ['--no-gui'], arguments: ['--no-gui'],
qt: { qt: {
onExit: onExitMock, onExit: onExitMock,
entryFunction: createQtAppInstance, entryFunction: tst_qtloader_integration_entry,
} }
}); });
try { try {
@ -316,7 +338,7 @@ export class QtLoaderIntegrationTests
arguments: ['--no-gui', '--crash-immediately'], arguments: ['--no-gui', '--crash-immediately'],
qt: { qt: {
onExit: onExitMock, onExit: onExitMock,
entryFunction: createQtAppInstance, entryFunction: tst_qtloader_integration_entry,
} }
}); });
} catch (e) { } catch (e) {
@ -340,7 +362,7 @@ export class QtLoaderIntegrationTests
arguments: ['--no-gui'], arguments: ['--no-gui'],
onAbort: onAbortMock, onAbort: onAbortMock,
qt: { qt: {
entryFunction: createQtAppInstance, entryFunction: tst_qtloader_integration_entry,
} }
}); });
try { try {
@ -358,7 +380,7 @@ export class QtLoaderIntegrationTests
arguments: ['--no-gui'], arguments: ['--no-gui'],
qt: { qt: {
onExit: onExitMock, onExit: onExitMock,
entryFunction: createQtAppInstance, entryFunction: tst_qtloader_integration_entry,
} }
}); });
// The module is running. onExit should not have been called. // The module is running. onExit should not have been called.
@ -383,7 +405,7 @@ export class QtLoaderIntegrationTests
arguments: ['--no-gui', '--exit-immediately'], arguments: ['--no-gui', '--exit-immediately'],
qt: { qt: {
onExit: onExitMock, onExit: onExitMock,
entryFunction: createQtAppInstance, entryFunction: tst_qtloader_integration_entry,
} }
}); });
assert.equal(1, onExitMock.calls.length); assert.equal(1, onExitMock.calls.length);
@ -402,7 +424,7 @@ export class QtLoaderIntegrationTests
arguments: ['--no-gui'], arguments: ['--no-gui'],
quit: quitMock, quit: quitMock,
qt: { qt: {
entryFunction: createQtAppInstance, entryFunction: tst_qtloader_integration_entry,
} }
}); });
try { try {

View File

@ -48,7 +48,7 @@ Finally provide an html file which hosts the test runner and calls runTestCase()
<script type="text/javascript" src="test_case.js"></script> <script type="text/javascript" src="test_case.js"></script>
<script> <script>
window.onload = async () => { window.onload = async () => {
runTestCase(document.getElementById("log")); runTestCase(entryFunction, document.getElementById("log"));
}; };
</script> </script>
<p>Running Foo auto test.</p> <p>Running Foo auto test.</p>
@ -67,7 +67,7 @@ html file provides container elements which becomes QScreens for the test code.
window.onload = async () => { window.onload = async () => {
let log = document.getElementById("log") let log = document.getElementById("log")
let containers = [document.getElementById("container")]; let containers = [document.getElementById("container")];
runTestCase(log, containers); runTestCase(entryFunction, log, containers);
}; };
</script> </script>
<p>Running Foo auto test.</p> <p>Running Foo auto test.</p>

View File

@ -77,12 +77,13 @@ async function runTestFunction(instance, name) {
} }
} }
async function runTestCaseImpl(testFunctionStarted, testFunctionCompleted, qtContainers) { async function runTestCaseImpl(entryFunction, testFunctionStarted, testFunctionCompleted, qtContainers)
{
// Create test case instance // Create test case instance
const config = { const config = {
qtContainerElements: qtContainers || [] qtContainerElements: qtContainers || []
} }
const instance = await createQtAppInstance(config); const instance = await entryFunction(config);
// Run all test functions // Run all test functions
const functionsString = instance.getTestFunctions(); const functionsString = instance.getTestFunctions();
@ -124,10 +125,11 @@ function testFunctionCompleted(status) {
g_htmlLogElement.innerHTML += line; g_htmlLogElement.innerHTML += line;
} }
async function runTestCase(htmlLogElement, qtContainers) { async function runTestCase(entryFunction, htmlLogElement, qtContainers)
{
g_htmlLogElement = htmlLogElement; g_htmlLogElement = htmlLogElement;
try { try {
await runTestCaseImpl(testFunctionStarted, testFunctionCompleted, qtContainers); await runTestCaseImpl(entryFunction, testFunctionStarted, testFunctionCompleted, qtContainers);
g_htmlLogElement.innerHTML += "<br> DONE" g_htmlLogElement.innerHTML += "<br> DONE"
} catch (err) { } catch (err) {
g_htmlLogElement.innerHTML += err g_htmlLogElement.innerHTML += err

View File

@ -109,18 +109,18 @@ export class CompiledModule {
this.#resourceLocator = resourceLocator; this.#resourceLocator = resourceLocator;
} }
static make(js, wasm, resourceLocator static make(js, wasm, entryFunctionName, resourceLocator
) { ) {
const exports = {}; const exports = {};
eval(js); eval(js);
if (!exports.createQtAppInstance) { if (!exports[entryFunctionName]) {
throw new Error( throw new Error(
'createQtAppInstance has not been exported by the main script' '${entryFunctionName} has not been exported by the main script'
); );
} }
return new CompiledModule( return new CompiledModule(
exports.createQtAppInstance, js, wasm, resourceLocator exports[entryFunctionName], js, wasm, resourceLocator
); );
} }
@ -218,6 +218,6 @@ export class ModuleLoader {
); );
const [js, wasm] = await Promise.all([jsLoadPromise, wasmLoadPromise]); const [js, wasm] = await Promise.all([jsLoadPromise, wasmLoadPromise]);
return CompiledModule.make(js, wasm, this.#resourceLocator); return CompiledModule.make(js, wasm, `${moduleName}_entry`, this.#resourceLocator);
} }
} }