|  | // 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 | 
|  |  | 
|  | #include "experimental/webgpu/platform/emscripten/emscripten_driver.h" | 
|  |  | 
|  | #include <emscripten.h> | 
|  |  | 
|  | #define IREE_HAL_WEBGPU_DEVICE_ID_DEFAULT 0 | 
|  |  | 
|  | //===----------------------------------------------------------------------===// | 
|  | // Driver and device options | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | IREE_API_EXPORT void iree_hal_webgpu_driver_options_initialize( | 
|  | iree_hal_webgpu_driver_options_t* out_options) { | 
|  | IREE_ASSERT_ARGUMENT(out_options); | 
|  | memset(out_options, 0, sizeof(*out_options)); | 
|  |  | 
|  | out_options->backend_preference = IREE_HAL_WEBGPU_DRIVER_BACKEND_ANY; | 
|  | out_options->log_level = IREE_HAL_WEBGPU_DRIVER_LOG_LEVEL_OFF; | 
|  |  | 
|  | // TODO(benvanik): coming in future spec update. For now go high-perf. | 
|  | // out_options->power_preference = WGPUPowerPreference_Undefined; | 
|  | out_options->power_preference = WGPUPowerPreference_HighPerformance; | 
|  |  | 
|  | iree_hal_webgpu_device_options_initialize(&out_options->device_options); | 
|  | } | 
|  |  | 
|  | //===----------------------------------------------------------------------===// | 
|  | // Synchronous adapter and device request functions | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | // We use Asyncify for convenience - for now. | 
|  | //   https://emscripten.org/docs/porting/asyncify.html | 
|  | //   https://github.com/emscripten-core/emscripten/issues/15746 | 
|  | //   https://github.com/juj/wasm_webgpu/blob/main/lib/lib_webgpu.h | 
|  | // | 
|  | // The requestAdapter and requestDevice functions are asynchronous, while the | 
|  | // HAL API has synchronous driver and device creation. We want to support both | 
|  | // platform-independent tests (CTS tests, 'check' framework tests) and user | 
|  | // applications. For platform-independent tests, we keep the APIs synchronous | 
|  | // by using Asyncify. For user applications, we could pass in a loop or add | 
|  | // asynchronous APIs that let those applications use async/await directly. | 
|  | // | 
|  | // An even simpler solution for user applications is to request an adapter and | 
|  | // and device purely up in JavaScript, then to pass the already created device | 
|  | // in via preinitializedWebGPUDevice / emscripten_webgpu_get_device(). | 
|  |  | 
|  | #ifdef EM_ASYNC_JS | 
|  |  | 
|  | EM_ASYNC_JS(WGPUAdapter, wgpuInstanceRequestAdapterSync, (), { | 
|  | // TODO(scotttodd): WGPURequestAdapterOptions struct | 
|  | const adapter = await navigator['gpu']['requestAdapter'](); | 
|  | // WARNING: this calls functions directly on Emscripten's library_webgpu.js. | 
|  | // This is not a stable API! | 
|  | const adapterId = WebGPU.mgrAdapter.create(adapter); | 
|  | return adapterId; | 
|  | }); | 
|  |  | 
|  | EM_ASYNC_JS(WGPUDevice, wgpuAdapterRequestDeviceSync, (WGPUAdapter adapterId), { | 
|  | // WARNING: this calls functions directly on Emscripten's library_webgpu.js. | 
|  | // This is not a stable API! | 
|  | const adapter = WebGPU.mgrAdapter.get(adapterId); | 
|  |  | 
|  | // TODO(scotttodd): WGPUDeviceDescriptor struct | 
|  | const descriptor = {}; | 
|  | const device = await adapter['requestDevice'](descriptor); | 
|  |  | 
|  | const deviceWrapper = {queueId : WebGPU.mgrQueue.create(device["queue"])}; | 
|  | const deviceId = WebGPU.mgrDevice.create(device, deviceWrapper); | 
|  | return deviceId; | 
|  | }); | 
|  |  | 
|  | #else | 
|  |  | 
|  | WGPUAdapter wgpuInstanceRequestAdapterSync() { | 
|  | fprintf(stderr, "wgpuInstanceRequestAdapterSync requires -sASYNCIFY\n"); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | WGPUDevice wgpuAdapterRequestDeviceSync(WGPUAdapter adapterId) { | 
|  | fprintf(stderr, "wgpuAdapterRequestDeviceSync requires -sASYNCIFY\n"); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | #endif  // EM_ASYNC_JS | 
|  |  | 
|  | //===----------------------------------------------------------------------===// | 
|  | // iree_hal_webgpu_emscripten_driver_t | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | typedef struct iree_hal_webgpu_emscripten_driver_t { | 
|  | iree_hal_resource_t resource; | 
|  | iree_allocator_t host_allocator; | 
|  |  | 
|  | iree_string_view_t identifier; | 
|  | iree_hal_webgpu_device_options_t default_options; | 
|  |  | 
|  | WGPUInstance instance; | 
|  | WGPUAdapter adapter; | 
|  | } iree_hal_webgpu_emscripten_driver_t; | 
|  |  | 
|  | static const iree_hal_driver_vtable_t iree_hal_webgpu_emscripten_driver_vtable; | 
|  |  | 
|  | static iree_hal_webgpu_emscripten_driver_t* | 
|  | iree_hal_webgpu_emscripten_driver_cast(iree_hal_driver_t* base_value) { | 
|  | IREE_HAL_ASSERT_TYPE(base_value, &iree_hal_webgpu_emscripten_driver_vtable); | 
|  | return (iree_hal_webgpu_emscripten_driver_t*)base_value; | 
|  | } | 
|  |  | 
|  | iree_status_t iree_hal_webgpu_emscripten_driver_create( | 
|  | iree_string_view_t identifier, | 
|  | const iree_hal_webgpu_driver_options_t* options, | 
|  | iree_allocator_t host_allocator, iree_hal_driver_t** out_driver) { | 
|  | IREE_ASSERT_ARGUMENT(options); | 
|  | IREE_ASSERT_ARGUMENT(out_driver); | 
|  | *out_driver = NULL; | 
|  | IREE_TRACE_ZONE_BEGIN(z0); | 
|  |  | 
|  | iree_hal_webgpu_emscripten_driver_t* driver = NULL; | 
|  | iree_host_size_t total_size = sizeof(*driver) + identifier.size + /*NUL=*/1; | 
|  | IREE_RETURN_AND_END_ZONE_IF_ERROR( | 
|  | z0, iree_allocator_malloc(host_allocator, total_size, (void**)&driver)); | 
|  | iree_hal_resource_initialize(&iree_hal_webgpu_emscripten_driver_vtable, | 
|  | &driver->resource); | 
|  | driver->host_allocator = host_allocator; | 
|  |  | 
|  | iree_string_view_append_to_buffer(identifier, &driver->identifier, | 
|  | (char*)driver + sizeof(*driver)); | 
|  | memcpy(&driver->default_options, &options->device_options, | 
|  | sizeof(driver->default_options)); | 
|  |  | 
|  | const WGPUInstanceDescriptor instance_descriptor = { | 
|  | .nextInChain = NULL, | 
|  | }; | 
|  | driver->instance = wgpuCreateInstance(&instance_descriptor); | 
|  | if (!driver->instance) { | 
|  | iree_hal_driver_release((iree_hal_driver_t*)driver); | 
|  | IREE_TRACE_ZONE_END(z0); | 
|  | return iree_make_status( | 
|  | IREE_STATUS_UNAVAILABLE, | 
|  | "WebGPU implementation not present or failed to load"); | 
|  | } | 
|  |  | 
|  | // Request an adapter from the implementation. We only get one of these and it | 
|  | // may expose multiple devices so it's effectively what we consider a driver. | 
|  | // HACKS: sync via Asyncify | 
|  | WGPUAdapter adapter = wgpuInstanceRequestAdapterSync(); | 
|  | if (!adapter) { | 
|  | iree_hal_driver_release((iree_hal_driver_t*)driver); | 
|  | IREE_TRACE_ZONE_END(z0); | 
|  | return iree_make_status( | 
|  | IREE_STATUS_UNAVAILABLE, | 
|  | "WebGPU requestAdapter() failed to return a WGPUAdapter"); | 
|  | } | 
|  | driver->adapter = adapter; | 
|  |  | 
|  | WGPUAdapterProperties adapter_props; | 
|  | memset(&adapter_props, 0, sizeof(adapter_props)); | 
|  | wgpuAdapterGetProperties(driver->adapter, &adapter_props); | 
|  |  | 
|  | *out_driver = (iree_hal_driver_t*)driver; | 
|  |  | 
|  | IREE_TRACE_ZONE_END(z0); | 
|  | return iree_ok_status(); | 
|  | } | 
|  |  | 
|  | static void iree_hal_webgpu_emscripten_driver_destroy( | 
|  | iree_hal_driver_t* base_driver) { | 
|  | iree_hal_webgpu_emscripten_driver_t* driver = | 
|  | iree_hal_webgpu_emscripten_driver_cast(base_driver); | 
|  | iree_allocator_t host_allocator = driver->host_allocator; | 
|  | IREE_TRACE_ZONE_BEGIN(z0); | 
|  |  | 
|  | // TODO(scotttodd): emscripten teardown? | 
|  | // driver->adapter = NULL; | 
|  | // driver->instance = NULL; | 
|  |  | 
|  | iree_allocator_free(host_allocator, driver); | 
|  |  | 
|  | IREE_TRACE_ZONE_END(z0); | 
|  | } | 
|  |  | 
|  | static iree_status_t iree_hal_webgpu_emscripten_driver_query_available_devices( | 
|  | iree_hal_driver_t* base_driver, iree_allocator_t allocator, | 
|  | iree_host_size_t* out_device_info_count, | 
|  | iree_hal_device_info_t** out_device_infos) { | 
|  | // Unfortunately no queries in WebGPU; we can only request a single device. | 
|  | static const iree_hal_device_info_t device_infos[1] = { | 
|  | { | 
|  | .device_id = IREE_HAL_WEBGPU_DEVICE_ID_DEFAULT, | 
|  | .name = iree_string_view_literal("default"), | 
|  | }, | 
|  | }; | 
|  | *out_device_info_count = IREE_ARRAYSIZE(device_infos); | 
|  | return iree_allocator_clone( | 
|  | allocator, iree_make_const_byte_span(device_infos, sizeof(device_infos)), | 
|  | (void**)out_device_infos); | 
|  | } | 
|  |  | 
|  | static iree_status_t iree_hal_webgpu_emscripten_driver_dump_device_info( | 
|  | iree_hal_driver_t* base_driver, iree_hal_device_id_t device_id, | 
|  | iree_string_builder_t* builder) { | 
|  | iree_hal_webgpu_emscripten_driver_t* driver = | 
|  | iree_hal_webgpu_emscripten_driver_cast(base_driver); | 
|  | // TODO(scotttodd): dump detailed device info. | 
|  | (void)driver; | 
|  | return iree_ok_status(); | 
|  | } | 
|  |  | 
|  | static iree_status_t iree_hal_webgpu_emscripten_driver_create_device_by_id( | 
|  | iree_hal_driver_t* base_driver, iree_hal_device_id_t device_id, | 
|  | iree_host_size_t param_count, const iree_string_pair_t* params, | 
|  | iree_allocator_t host_allocator, iree_hal_device_t** out_device) { | 
|  | iree_hal_webgpu_emscripten_driver_t* driver = | 
|  | iree_hal_webgpu_emscripten_driver_cast(base_driver); | 
|  |  | 
|  | // HACKS: sync via Asyncify | 
|  | WGPUDevice device = wgpuAdapterRequestDeviceSync(driver->adapter); | 
|  | if (!device) { | 
|  | return iree_make_status( | 
|  | IREE_STATUS_UNAVAILABLE, | 
|  | "WebGPU requestDevice() failed to return a WGPUDevice"); | 
|  | } | 
|  |  | 
|  | return iree_hal_webgpu_wrap_device(driver->identifier, | 
|  | &driver->default_options, device, | 
|  | driver->host_allocator, out_device); | 
|  | } | 
|  |  | 
|  | static iree_status_t iree_hal_webgpu_emscripten_driver_create_device_by_path( | 
|  | iree_hal_driver_t* base_driver, iree_string_view_t driver_name, | 
|  | iree_string_view_t device_path, iree_host_size_t param_count, | 
|  | const iree_string_pair_t* params, iree_allocator_t host_allocator, | 
|  | iree_hal_device_t** out_device) { | 
|  | if (!iree_string_view_is_empty(device_path)) { | 
|  | return iree_make_status(IREE_STATUS_NOT_FOUND, | 
|  | "device paths not yet implemented"); | 
|  | } | 
|  | return iree_hal_webgpu_emscripten_driver_create_device_by_id( | 
|  | base_driver, IREE_HAL_DEVICE_ID_DEFAULT, param_count, params, | 
|  | host_allocator, out_device); | 
|  | } | 
|  |  | 
|  | static const iree_hal_driver_vtable_t iree_hal_webgpu_emscripten_driver_vtable = | 
|  | { | 
|  | .destroy = iree_hal_webgpu_emscripten_driver_destroy, | 
|  | .query_available_devices = | 
|  | iree_hal_webgpu_emscripten_driver_query_available_devices, | 
|  | .dump_device_info = iree_hal_webgpu_emscripten_driver_dump_device_info, | 
|  | .create_device_by_id = | 
|  | iree_hal_webgpu_emscripten_driver_create_device_by_id, | 
|  | .create_device_by_path = | 
|  | iree_hal_webgpu_emscripten_driver_create_device_by_path, | 
|  | }; |