diff options
Diffstat (limited to 'util/wasm/batchedtestrunner/qwasmjsruntime.js')
-rw-r--r-- | util/wasm/batchedtestrunner/qwasmjsruntime.js | 230 |
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); + } +} |