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_link_options("${wasmTarget}" INTERFACE "SHELL:-s MEMORY64=1" -mwasm64)
endif()
# Enable MODULARIZE and set EXPORT_NAME, which makes it possible to
# create application instances using a global constructor function,
# e.g. let app_instance = await createQtAppInstance().
# (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")
# Enable MODULARIZE so that we are able to set EXPORT_NAME later and instantiate on demand (with
# MODULARIZE=0, emscripten creates a global app instance object at Javascript eval time)
target_link_options("${wasmTarget}" INTERFACE "SHELL:-s MODULARIZE=1")
#simd
if (QT_FEATURE_wasm_simd128)
@ -126,6 +121,7 @@ function (qt_internal_setup_wasm_target_properties wasmTarget)
endfunction()
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_wasm_add_target_helpers ${target})
endfunction()

View File

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

View File

@ -15,6 +15,13 @@ exists($$QMAKE_QT_CONFIG) {
}
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) {
EMCC_LFLAGS += -pthread

View File

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

View File

@ -104,3 +104,12 @@ function(_qt_internal_add_wasm_extra_exported_methods target)
)
endif()
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')
throw new Error('config.qt is required, expected an object');
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;
delete config.qt.containerElements;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -48,10 +48,32 @@ export class QtLoaderIntegrationTests
'config.qt is required, expected an object', caughtException.message);
}
async useDefaultOnMissingEntryFunction()
async missingEntryFunction()
{
const instance = await qtLoad({ arguments: ['--no-gui'], qt: {}});
assert.isNotUndefined(instance);
let caughtException;
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()
@ -62,7 +84,7 @@ export class QtLoaderIntegrationTests
variable1: 'value1',
variable2: 'value2'
},
entryFunction: createQtAppInstance,
entryFunction: tst_qtloader_integration_entry,
containerElements: [this.#testScreenContainers[0]]
}
});
@ -79,7 +101,7 @@ export class QtLoaderIntegrationTests
const instance = await qtLoad({
qt: {
entryFunction: createQtAppInstance,
entryFunction: tst_qtloader_integration_entry,
containerElements: this.#testScreenContainers
}
});
@ -125,7 +147,7 @@ export class QtLoaderIntegrationTests
{
const instance = await qtLoad({
qt: {
entryFunction: createQtAppInstance,
entryFunction: tst_qtloader_integration_entry,
containerElements: this.#testScreenContainers,
}
});
@ -181,7 +203,7 @@ export class QtLoaderIntegrationTests
const instances = await Promise.all([1, 2, 3].map(i => qtLoad({
qt: {
entryFunction: createQtAppInstance,
entryFunction: tst_qtloader_integration_entry,
containerElements: [this.#addScreenContainer(`screen-container-${i}`, {
width: `${i * 10}px`,
height: `${i * 10}px`,
@ -222,7 +244,7 @@ export class QtLoaderIntegrationTests
accumulatedStdout += output;
},
qt: {
entryFunction: createQtAppInstance,
entryFunction: tst_qtloader_integration_entry,
}
});
@ -246,7 +268,7 @@ export class QtLoaderIntegrationTests
{
await qtLoad({
qt: {
entryFunction: createQtAppInstance,
entryFunction: tst_qtloader_integration_entry,
containerElements: [this.#testScreenContainers[0]],
module: await WebAssembly.compileStreaming(
fetch('tst_qtloader_integration.wasm'))
@ -259,7 +281,7 @@ export class QtLoaderIntegrationTests
const instance = await qtLoad({
arguments: ['--no-gui', 'arg1', 'other', 'yetanotherarg'],
qt: {
entryFunction: createQtAppInstance,
entryFunction: tst_qtloader_integration_entry,
}
});
const args = this.#callTestInstanceApi(instance, 'retrieveArguments');
@ -275,7 +297,7 @@ export class QtLoaderIntegrationTests
try {
await qtLoad({
qt: {
entryFunction: createQtAppInstance,
entryFunction: tst_qtloader_integration_entry,
containerElements: [this.#testScreenContainers[0]],
module: Promise.reject(new Error('Failed to load')),
}
@ -294,7 +316,7 @@ export class QtLoaderIntegrationTests
arguments: ['--no-gui'],
qt: {
onExit: onExitMock,
entryFunction: createQtAppInstance,
entryFunction: tst_qtloader_integration_entry,
}
});
try {
@ -316,7 +338,7 @@ export class QtLoaderIntegrationTests
arguments: ['--no-gui', '--crash-immediately'],
qt: {
onExit: onExitMock,
entryFunction: createQtAppInstance,
entryFunction: tst_qtloader_integration_entry,
}
});
} catch (e) {
@ -340,7 +362,7 @@ export class QtLoaderIntegrationTests
arguments: ['--no-gui'],
onAbort: onAbortMock,
qt: {
entryFunction: createQtAppInstance,
entryFunction: tst_qtloader_integration_entry,
}
});
try {
@ -358,7 +380,7 @@ export class QtLoaderIntegrationTests
arguments: ['--no-gui'],
qt: {
onExit: onExitMock,
entryFunction: createQtAppInstance,
entryFunction: tst_qtloader_integration_entry,
}
});
// The module is running. onExit should not have been called.
@ -383,7 +405,7 @@ export class QtLoaderIntegrationTests
arguments: ['--no-gui', '--exit-immediately'],
qt: {
onExit: onExitMock,
entryFunction: createQtAppInstance,
entryFunction: tst_qtloader_integration_entry,
}
});
assert.equal(1, onExitMock.calls.length);
@ -402,7 +424,7 @@ export class QtLoaderIntegrationTests
arguments: ['--no-gui'],
quit: quitMock,
qt: {
entryFunction: createQtAppInstance,
entryFunction: tst_qtloader_integration_entry,
}
});
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>
window.onload = async () => {
runTestCase(document.getElementById("log"));
runTestCase(entryFunction, document.getElementById("log"));
};
</script>
<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 () => {
let log = document.getElementById("log")
let containers = [document.getElementById("container")];
runTestCase(log, containers);
runTestCase(entryFunction, log, containers);
};
</script>
<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
const config = {
qtContainerElements: qtContainers || []
}
const instance = await createQtAppInstance(config);
const instance = await entryFunction(config);
// Run all test functions
const functionsString = instance.getTestFunctions();
@ -124,10 +125,11 @@ function testFunctionCompleted(status) {
g_htmlLogElement.innerHTML += line;
}
async function runTestCase(htmlLogElement, qtContainers) {
async function runTestCase(entryFunction, htmlLogElement, qtContainers)
{
g_htmlLogElement = htmlLogElement;
try {
await runTestCaseImpl(testFunctionStarted, testFunctionCompleted, qtContainers);
await runTestCaseImpl(entryFunction, testFunctionStarted, testFunctionCompleted, qtContainers);
g_htmlLogElement.innerHTML += "<br> DONE"
} catch (err) {
g_htmlLogElement.innerHTML += err

View File

@ -109,18 +109,18 @@ export class CompiledModule {
this.#resourceLocator = resourceLocator;
}
static make(js, wasm, resourceLocator
static make(js, wasm, entryFunctionName, resourceLocator
) {
const exports = {};
eval(js);
if (!exports.createQtAppInstance) {
if (!exports[entryFunctionName]) {
throw new Error(
'createQtAppInstance has not been exported by the main script'
'${entryFunctionName} has not been exported by the main script'
);
}
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]);
return CompiledModule.make(js, wasm, this.#resourceLocator);
return CompiledModule.make(js, wasm, `${moduleName}_entry`, this.#resourceLocator);
}
}