|  | // Copyright 2022 The IREE Authors | 
|  | // | 
|  | // Licensed under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  |  | 
|  | // Promise-based API for interacting with the IREE runtime. | 
|  |  | 
|  | const EMSCRIPTEN_SCRIPT_URL = 'web-sample-webgpu.js'; | 
|  |  | 
|  | // ------------------------------------------------------------------------- // | 
|  | // - API                                                                   - // | 
|  | // ------------------------------------------------------------------------- // | 
|  |  | 
|  | // Initializes IREE's runtime. | 
|  | async function ireeInitialize() { | 
|  | return _ireeInitialize(); | 
|  | } | 
|  |  | 
|  | // Loads an IREE program stored in a .vmfb file. | 
|  | // | 
|  | // Accepts either a string path to a file (XMLHttpRequest compatible) or an | 
|  | // ArrayBuffer containing an already loaded file. | 
|  | // | 
|  | // In order to call functions on the program it must be compiled in a supported | 
|  | // configuration, such as with these flags: | 
|  | //     --iree-hal-target-backends=webgpu-spirv | 
|  | // | 
|  | // Resolves with an opaque pointer to the program state on success. | 
|  | async function ireeLoadProgram(vmfbPathOrBuffer) { | 
|  | return _ireeLoadProgram(vmfbPathOrBuffer); | 
|  | } | 
|  |  | 
|  | // Inspects a program. | 
|  | async function ireeInspectProgram(programState) { | 
|  | return _ireeInspectProgram(programState); | 
|  | } | 
|  |  | 
|  | // Unloads a program. | 
|  | async function ireeUnloadProgram(programState) { | 
|  | return _ireeUnloadProgram(programState); | 
|  | } | 
|  |  | 
|  | // Calls a function on a loaded program. | 
|  | // | 
|  | // Resolves with a parsed JSON object on success: | 
|  | // { | 
|  | //   "invoke_time_ms": [number], | 
|  | //   "readback_time_ms": [number], | 
|  | //   "outputs": [semicolon delimited list of formatted outputs] | 
|  | // } | 
|  | async function ireeCallFunction( | 
|  | programState, functionName, inputs, iterations) { | 
|  | return _ireeCallFunction(programState, functionName, inputs, iterations); | 
|  | } | 
|  |  | 
|  | // ------------------------------------------------------------------------- // | 
|  | // - Implementation                                                        - // | 
|  | // ------------------------------------------------------------------------- // | 
|  |  | 
|  | // TODO(scotttodd): namespace / scope these (don't pollute window object) | 
|  | let wasmSetupSampleFn; | 
|  | let wasmCleanupSampleFn; | 
|  | let wasmLoadProgramFn; | 
|  | let wasmInspectProgramFn; | 
|  | let wasmUnloadProgramFn; | 
|  | let wasmCallFunctionFn; | 
|  |  | 
|  | let initializedPromise, initializePromiseResolve, initializePromiseReject; | 
|  | let sampleState; | 
|  |  | 
|  | var Module = { | 
|  | print: function(text) { | 
|  | console.log('(C)', text); | 
|  | }, | 
|  | printErr: function(text) { | 
|  | console.error('(C)', text); | 
|  | }, | 
|  | onRuntimeInitialized: function() { | 
|  | wasmSetupSampleFn = Module.cwrap('setup_sample', 'number', []); | 
|  | wasmCleanupSampleFn = Module.cwrap('cleanup_sample', null, ['number']); | 
|  | wasmLoadProgramFn = Module.cwrap( | 
|  | 'load_program', | 
|  | 'number', | 
|  | ['number', 'number', 'number'], | 
|  | ); | 
|  | wasmInspectProgramFn = Module.cwrap('inspect_program', null, ['number']); | 
|  | wasmUnloadProgramFn = Module.cwrap('unload_program', null, ['number']); | 
|  | wasmCallFunctionFn = Module.cwrap( | 
|  | 'call_function', | 
|  | 'number', | 
|  | ['number', 'string', 'string', 'number'], | 
|  | ); | 
|  |  | 
|  | sampleState = wasmSetupSampleFn(); | 
|  | if (!sampleState) { | 
|  | initializePromiseReject('Runtime initialization failed'); | 
|  | return; | 
|  | } | 
|  | initializePromiseResolve(); | 
|  | }, | 
|  | noInitialRun: true, | 
|  | }; | 
|  |  | 
|  | async function _ireeInitialize() { | 
|  | if (initializedPromise) return initializedPromise; | 
|  |  | 
|  | initializedPromise = new Promise((resolve, reject) => { | 
|  | initializePromiseResolve = resolve; | 
|  | initializePromiseReject = reject; | 
|  | }); | 
|  |  | 
|  | // Preinitialize a WebGPU device here. We could let the C program request the | 
|  | // adapter and device itself, but that would jump through layers of Emscripten | 
|  | // binding code and C/JS callbacks. This is much more concise. | 
|  | // const instance = -1; // No wgpuCreateInstance function in JS (yet?). | 
|  | if (navigator['gpu'] === undefined) { | 
|  | throw 'No \'gpu\' property on navigator, can\'t initialize WebGPU (missing #enable-unsafe-webgpu or an origin trial?)'; | 
|  | } | 
|  | const adapter = await navigator['gpu']['requestAdapter'](); | 
|  | const deviceDescriptor = { | 
|  | 'label': 'IREE WebGPU device', | 
|  | 'requiredFeatures': [], | 
|  | 'requiredLimits': { | 
|  | 'maxBindGroups': 4, | 
|  | 'maxStorageBuffersPerShaderStage': 8, | 
|  | }, | 
|  | 'defaultQueue': {}, | 
|  | }; | 
|  | const device = await adapter['requestDevice'](deviceDescriptor); | 
|  | // Emscripten makes this available via emscripten_webgpu_get_device() in C. | 
|  | Module['preinitializedWebGPUDevice'] = device; | 
|  |  | 
|  | const mainScript = document.createElement('script'); | 
|  | mainScript.setAttribute('src', EMSCRIPTEN_SCRIPT_URL); | 
|  | document.body.appendChild(mainScript); | 
|  |  | 
|  | return initializedPromise; | 
|  | } | 
|  |  | 
|  | function _ireeLoadProgramBuffer(programDataBuffer) { | 
|  | const programDataView = new Int8Array(programDataBuffer); | 
|  |  | 
|  | const programDataWasmBuffer = Module._malloc( | 
|  | programDataView.length * programDataView.BYTES_PER_ELEMENT); | 
|  | Module.HEAP8.set(programDataView, programDataWasmBuffer); | 
|  |  | 
|  | // Note: we transfer ownership of the FlatBuffer data here, so there is | 
|  | // no need to call `Module._free(programDataWasmBuffer)` later. | 
|  | const programState = wasmLoadProgramFn( | 
|  | sampleState, programDataWasmBuffer, programDataBuffer.byteLength); | 
|  | return programState; | 
|  | } | 
|  |  | 
|  | function _ireeLoadProgram(vmfbPathOrBuffer) { | 
|  | if (vmfbPathOrBuffer instanceof ArrayBuffer) { | 
|  | const programState = _ireeLoadProgramBuffer(vmfbPathOrBuffer); | 
|  | if (programState !== 0) { | 
|  | return Promise.resolve(programState); | 
|  | } else { | 
|  | return Promise.reject('Wasm module error loading program'); | 
|  | } | 
|  | } | 
|  |  | 
|  | return new Promise((resolve, reject) => { | 
|  | const fetchRequest = new XMLHttpRequest(); | 
|  | fetchRequest.onload = function(progressEvent) { | 
|  | const programState = | 
|  | _ireeLoadProgramBuffer(progressEvent.target.response); | 
|  | if (programState !== 0) { | 
|  | resolve(programState); | 
|  | } else { | 
|  | reject('Wasm module error loading program'); | 
|  | } | 
|  | }; | 
|  | fetchRequest.onerror = function(progressEvent) { | 
|  | reject(progressEvent.error); | 
|  | }; | 
|  | fetchRequest.open('GET', vmfbPathOrBuffer); | 
|  | fetchRequest.responseType = 'arraybuffer'; | 
|  | fetchRequest.send(); | 
|  | }); | 
|  | } | 
|  |  | 
|  | function _ireeInspectProgram(programState) { | 
|  | wasmInspectProgramFn(programState); | 
|  | return Promise.resolve(); | 
|  | } | 
|  |  | 
|  | function _ireeUnloadProgram(programState) { | 
|  | wasmUnloadProgramFn(programState); | 
|  | return Promise.resolve(); | 
|  | } | 
|  |  | 
|  | function _ireeCallFunction(programState, functionName, inputs) { | 
|  | let inputsJoined; | 
|  | if (Array.isArray(inputs)) { | 
|  | inputsJoined = inputs.join(';'); | 
|  | } else if (typeof (inputs) === 'string') { | 
|  | inputsJoined = inputs; | 
|  | } else { | 
|  | return Promise.reject( | 
|  | 'Expected \'inputs\' to be a String or an array of Strings'); | 
|  | } | 
|  |  | 
|  | return new Promise((resolve, reject) => { | 
|  | const completionCallbackFunction = addFunction((resultPtr) => { | 
|  | if (resultPtr === 0) { | 
|  | reject('Error from callback when calling function'); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const resultStr = Module.UTF8ToString(resultPtr); | 
|  | Module._free(resultPtr); | 
|  | resolve(JSON.parse(resultStr)); | 
|  | }, 'vi'); | 
|  |  | 
|  | const immediateResult = wasmCallFunctionFn( | 
|  | programState, functionName, inputsJoined, completionCallbackFunction); | 
|  | if (!immediateResult) { | 
|  | reject('Immediate error calling function'); | 
|  | } | 
|  | }); | 
|  | } |