| // 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, |
| }; |