summaryrefslogtreecommitdiffstats
path: root/util/wasm/batchedtestrunner/qwasmjsruntime.js
diff options
context:
space:
mode:
Diffstat (limited to 'util/wasm/batchedtestrunner/qwasmjsruntime.js')
-rw-r--r--util/wasm/batchedtestrunner/qwasmjsruntime.js230
1 files changed, 230 insertions, 0 deletions
diff --git a/util/wasm/batchedtestrunner/qwasmjsruntime.js b/util/wasm/batchedtestrunner/qwasmjsruntime.js
new file mode 100644
index 0000000000..e167c87d4a
--- /dev/null
+++ b/util/wasm/batchedtestrunner/qwasmjsruntime.js
@@ -0,0 +1,230 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+// Exposes platform capabilities as static properties
+
+export class AbortedError extends Error {
+ constructor(stdout) {
+ super(`The program has been aborted`)
+
+ this.stdout = stdout;
+ }
+}
+export class Platform {
+ static #webAssemblySupported = typeof WebAssembly !== 'undefined';
+
+ static #canCompileStreaming = WebAssembly.compileStreaming !== 'undefined';
+
+ static #webGLSupported = (() => {
+ // We expect that WebGL is supported if WebAssembly is; however
+ // the GPU may be blacklisted.
+ try {
+ const canvas = document.createElement('canvas');
+ return !!(
+ window.WebGLRenderingContext &&
+ (canvas.getContext('webgl') || canvas.getContext('experimental-webgl'))
+ );
+ } catch (e) {
+ return false;
+ }
+ })();
+
+ static #canLoadQt = Platform.#webAssemblySupported && Platform.#webGLSupported;
+
+ static get webAssemblySupported() {
+ return this.#webAssemblySupported;
+ }
+ static get canCompileStreaming() {
+ return this.#canCompileStreaming;
+ }
+ static get webGLSupported() {
+ return this.#webGLSupported;
+ }
+ static get canLoadQt() {
+ return this.#canLoadQt;
+ }
+}
+
+// Locates a resource, based on its relative path
+export class ResourceLocator {
+ #rootPath;
+
+ constructor(rootPath) {
+ this.#rootPath = rootPath;
+ if (rootPath.length > 0 && !rootPath.endsWith('/')) rootPath += '/';
+ }
+
+ locate(relativePath) {
+ return this.#rootPath + relativePath;
+ }
+}
+
+// Allows fetching of resources, such as text resources or wasm modules.
+export class ResourceFetcher {
+ #locator;
+
+ constructor(locator) {
+ this.#locator = locator;
+ }
+
+ async fetchText(filePath) {
+ return (await this.#fetchRawResource(filePath)).text();
+ }
+
+ async fetchCompileWasm(filePath, onFetched) {
+ const fetchResponse = await this.#fetchRawResource(filePath);
+ onFetched?.();
+
+ if (Platform.canCompileStreaming) {
+ try {
+ return await WebAssembly.compileStreaming(fetchResponse);
+ } catch {
+ // NOOP - fallback to sequential fetching below
+ }
+ }
+ return WebAssembly.compile(await fetchResponse.arrayBuffer());
+ }
+
+ async #fetchRawResource(filePath) {
+ const response = await fetch(this.#locator.locate(filePath));
+ if (!response.ok)
+ throw new Error(
+ `${response.status} ${response.statusText} ${response.url}`
+ );
+ return response;
+ }
+}
+
+// Represents a WASM module, wrapping the instantiation and execution thereof.
+export class CompiledModule {
+ #createQtAppInstanceFn;
+ #js;
+ #wasm;
+ #resourceLocator;
+
+ constructor(createQtAppInstanceFn, js, wasm, resourceLocator) {
+ this.#createQtAppInstanceFn = createQtAppInstanceFn;
+ this.#js = js;
+ this.#wasm = wasm;
+ this.#resourceLocator = resourceLocator;
+ }
+
+ static make(js, wasm, resourceLocator
+ ) {
+ const exports = {};
+ eval(js);
+ if (!exports.createQtAppInstance) {
+ throw new Error(
+ 'createQtAppInstance has not been exported by the main script'
+ );
+ }
+
+ return new CompiledModule(
+ exports.createQtAppInstance, js, wasm, resourceLocator
+ );
+ }
+
+ async exec(parameters) {
+ return await new Promise(async (resolve, reject) => {
+ let instance = undefined;
+ let result = undefined;
+ const continuation = () => {
+ if (!(instance && result))
+ return;
+ resolve({
+ stdout: result.stdout,
+ exitCode: result.exitCode,
+ instance,
+ });
+ };
+
+ instance = await this.#createQtAppInstanceFn((() => {
+ const params = this.#makeDefaultExecParams({
+ onInstantiationError: (error) => { reject(error); },
+ });
+ params.arguments = parameters?.args;
+ let data = '';
+ params.print = (out) => {
+ if (parameters?.printStdout === true)
+ console.log(out);
+ data += `${out}\n`;
+ };
+ params.printErr = () => { };
+ params.onAbort = () => reject(new AbortedError(data));
+ params.quit = (code, exception) => {
+ if (exception && exception.name !== 'ExitStatus')
+ reject(exception);
+ result = { stdout: data, exitCode: code };
+ continuation();
+ };
+ return params;
+ })());
+ continuation();
+ });
+ }
+
+ #makeDefaultExecParams(params) {
+ const instanceParams = {};
+ instanceParams.instantiateWasm = async (imports, onDone) => {
+ try {
+ onDone(await WebAssembly.instantiate(this.#wasm, imports));
+ } catch (e) {
+ params?.onInstantiationError?.(e);
+ }
+ };
+ instanceParams.locateFile = (filename) =>
+ this.#resourceLocator.locate(filename);
+ instanceParams.monitorRunDependencies = (name) => { };
+ instanceParams.print = (text) => true && console.log(text);
+ instanceParams.printErr = (text) => true && console.warn(text);
+ instanceParams.preRun = [
+ (instance) => {
+ const env = {};
+ instance.ENV = env;
+ },
+ ];
+
+ instanceParams.mainScriptUrlOrBlob = new Blob([this.#js], {
+ type: 'text/javascript',
+ });
+ return instanceParams;
+ }
+}
+
+// Streamlines loading of WASM modules.
+export class ModuleLoader {
+ #fetcher;
+ #resourceLocator;
+
+ constructor(
+ fetcher,
+ resourceLocator
+ ) {
+ this.#fetcher = fetcher;
+ this.#resourceLocator = resourceLocator;
+ }
+
+ // Loads an emscripten module named |moduleName| from the main resource path. Provides
+ // progress of 'downloading' and 'compiling' to the caller using the |onProgress| callback.
+ async loadEmscriptenModule(
+ moduleName, onProgress
+ ) {
+ if (!Platform.webAssemblySupported)
+ throw new Error('Web assembly not supported');
+ if (!Platform.webGLSupported)
+ throw new Error('WebGL is not supported');
+
+ onProgress('downloading');
+
+ const jsLoadPromise = this.#fetcher.fetchText(`${moduleName}.js`);
+ const wasmLoadPromise = this.#fetcher.fetchCompileWasm(
+ `${moduleName}.wasm`,
+ () => {
+ onProgress('compiling');
+ }
+ );
+
+ const [js, wasm] = await Promise.all([jsLoadPromise, wasmLoadPromise]);
+ return CompiledModule.make(js, wasm, this.#resourceLocator);
+ }
+}