| // Copyright 2021 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/simple_allocator.h" | |
| #include <stddef.h> | |
| #include "experimental/webgpu/buffer.h" | |
| #include "experimental/webgpu/webgpu_device.h" | |
| #include "iree/base/api.h" | |
| typedef struct iree_hal_webgpu_simple_allocator_t { | |
| iree_hal_resource_t resource; | |
| iree_allocator_t host_allocator; | |
| iree_hal_device_t* device; | |
| iree_string_view_t identifier; | |
| IREE_STATISTICS(iree_hal_allocator_statistics_t statistics;) | |
| } iree_hal_webgpu_simple_allocator_t; | |
| extern const iree_hal_allocator_vtable_t | |
| iree_hal_webgpu_simple_allocator_vtable; | |
| static iree_hal_webgpu_simple_allocator_t* | |
| iree_hal_webgpu_simple_allocator_cast(iree_hal_allocator_t* base_value) { | |
| IREE_HAL_ASSERT_TYPE(base_value, &iree_hal_webgpu_simple_allocator_vtable); | |
| return (iree_hal_webgpu_simple_allocator_t*)base_value; | |
| } | |
| iree_status_t iree_hal_webgpu_simple_allocator_create( | |
| iree_hal_device_t* device, iree_string_view_t identifier, | |
| iree_allocator_t host_allocator, iree_hal_allocator_t** out_allocator) { | |
| IREE_ASSERT_ARGUMENT(device); | |
| IREE_ASSERT_ARGUMENT(out_allocator); | |
| *out_allocator = NULL; | |
| IREE_TRACE_ZONE_BEGIN(z0); | |
| iree_hal_webgpu_simple_allocator_t* allocator = NULL; | |
| iree_host_size_t struct_size = iree_sizeof_struct(*allocator); | |
| iree_host_size_t total_size = struct_size + identifier.size; | |
| iree_status_t status = | |
| iree_allocator_malloc(host_allocator, total_size, (void**)&allocator); | |
| if (iree_status_is_ok(status)) { | |
| iree_hal_resource_initialize(&iree_hal_webgpu_simple_allocator_vtable, | |
| &allocator->resource); | |
| allocator->host_allocator = host_allocator; | |
| allocator->device = device; | |
| iree_string_view_append_to_buffer(identifier, &allocator->identifier, | |
| (char*)allocator + struct_size); | |
| *out_allocator = (iree_hal_allocator_t*)allocator; | |
| } | |
| IREE_TRACE_ZONE_END(z0); | |
| return status; | |
| } | |
| static void iree_hal_webgpu_simple_allocator_destroy( | |
| iree_hal_allocator_t* IREE_RESTRICT base_allocator) { | |
| iree_hal_webgpu_simple_allocator_t* allocator = | |
| iree_hal_webgpu_simple_allocator_cast(base_allocator); | |
| iree_allocator_t host_allocator = allocator->host_allocator; | |
| IREE_TRACE_ZONE_BEGIN(z0); | |
| iree_allocator_free(host_allocator, allocator); | |
| IREE_TRACE_ZONE_END(z0); | |
| } | |
| static iree_allocator_t iree_hal_webgpu_simple_allocator_host_allocator( | |
| const iree_hal_allocator_t* IREE_RESTRICT base_allocator) { | |
| iree_hal_webgpu_simple_allocator_t* allocator = | |
| (iree_hal_webgpu_simple_allocator_t*)base_allocator; | |
| return allocator->host_allocator; | |
| } | |
| static iree_status_t iree_hal_webgpu_simple_allocator_trim( | |
| iree_hal_allocator_t* IREE_RESTRICT base_allocator) { | |
| return iree_ok_status(); | |
| } | |
| static void iree_hal_webgpu_simple_allocator_query_statistics( | |
| iree_hal_allocator_t* IREE_RESTRICT base_allocator, | |
| iree_hal_allocator_statistics_t* IREE_RESTRICT out_statistics) { | |
| IREE_STATISTICS({ | |
| iree_hal_webgpu_simple_allocator_t* allocator = | |
| iree_hal_webgpu_simple_allocator_cast(base_allocator); | |
| memcpy(out_statistics, &allocator->statistics, sizeof(*out_statistics)); | |
| }); | |
| } | |
| static iree_hal_buffer_compatibility_t | |
| iree_hal_webgpu_simple_allocator_query_buffer_compatibility( | |
| iree_hal_allocator_t* IREE_RESTRICT base_allocator, | |
| iree_hal_buffer_params_t* IREE_RESTRICT params, | |
| iree_device_size_t* IREE_RESTRICT allocation_size) { | |
| // TODO(benvanik): check to ensure the allocator can serve the memory type. | |
| // All buffers can be allocated on the heap. | |
| iree_hal_buffer_compatibility_t compatibility = | |
| IREE_HAL_BUFFER_COMPATIBILITY_ALLOCATABLE; | |
| // Buffers can only be used on the queue if they are device visible. | |
| if (iree_all_bits_set(params->type, IREE_HAL_MEMORY_TYPE_DEVICE_VISIBLE)) { | |
| if (iree_any_bit_set(params->usage, IREE_HAL_BUFFER_USAGE_TRANSFER)) { | |
| compatibility |= IREE_HAL_BUFFER_COMPATIBILITY_QUEUE_TRANSFER; | |
| } | |
| if (iree_any_bit_set(params->usage, | |
| IREE_HAL_BUFFER_USAGE_DISPATCH_STORAGE)) { | |
| compatibility |= IREE_HAL_BUFFER_COMPATIBILITY_QUEUE_DISPATCH; | |
| } | |
| } | |
| // WebGPU does not support synchronous buffer mapping, so disallow. | |
| if (iree_all_bits_set(params->usage, IREE_HAL_BUFFER_USAGE_MAPPING)) { | |
| return IREE_HAL_BUFFER_COMPATIBILITY_NONE; | |
| } | |
| // Guard against the corner case where the requested buffer size is 0. The | |
| // application is unlikely to do anything when requesting a 0-byte buffer; but | |
| // it can happen in real world use cases. So we should at least not crash. | |
| if (*allocation_size == 0) *allocation_size = 4; | |
| return compatibility; | |
| } | |
| static iree_status_t iree_hal_webgpu_simple_allocator_allocate_buffer( | |
| iree_hal_allocator_t* IREE_RESTRICT base_allocator, | |
| const iree_hal_buffer_params_t* IREE_RESTRICT params, | |
| iree_device_size_t allocation_size, iree_const_byte_span_t initial_data, | |
| iree_hal_buffer_t** IREE_RESTRICT out_buffer) { | |
| IREE_ASSERT_ARGUMENT(base_allocator); | |
| IREE_ASSERT_ARGUMENT(params); | |
| IREE_ASSERT_ARGUMENT(out_buffer); | |
| *out_buffer = NULL; | |
| iree_hal_webgpu_simple_allocator_t* allocator = | |
| iree_hal_webgpu_simple_allocator_cast(base_allocator); | |
| // Guard against the corner case where the requested buffer size is 0. The | |
| // application is unlikely to do anything when requesting a 0-byte buffer; but | |
| // it can happen in real world use cases. So we should at least not crash. | |
| if (allocation_size == 0) allocation_size = 4; | |
| WGPUBufferUsageFlags usage_flags = WGPUBufferUsage_None; | |
| if (iree_all_bits_set(params->usage, IREE_HAL_BUFFER_USAGE_TRANSFER)) { | |
| usage_flags |= WGPUBufferUsage_CopySrc; | |
| usage_flags |= WGPUBufferUsage_CopyDst; | |
| } | |
| if (iree_all_bits_set(params->usage, IREE_HAL_BUFFER_USAGE_MAPPING)) { | |
| // Requirements from https://gpuweb.github.io/gpuweb/#buffer-usage: | |
| // * MAP_WRITE can only be combined with COPY_SRC | |
| // * MAP_READ can only be combined with COPY_DST | |
| // | |
| // We don't have copy source/dest modeled in IREE's HAL (yet) so for now | |
| // we only enable mapping if transfer is set and hope it's not a copy dest. | |
| // Any copy dest buffers (such as for readback) must be allocated directly: | |
| // WGPUBufferDescriptor descriptor = { | |
| // ... | |
| // .usage = WGPUBufferUsage_MapRead | WGPUBufferUsage_CopyDst, | |
| // }; | |
| // buffer = wgpuDeviceCreateBuffer(device, descriptor); | |
| // iree_hal_webgpu_buffer_wrap(..., buffer, ...); | |
| if (iree_all_bits_set(params->usage, IREE_HAL_BUFFER_USAGE_TRANSFER) && | |
| !iree_any_bit_set(params->usage, | |
| IREE_HAL_BUFFER_USAGE_DISPATCH_STORAGE)) { | |
| usage_flags |= WGPUBufferUsage_MapWrite; | |
| usage_flags &= ~(WGPUBufferUsage_CopyDst); // Clear CopyDst | |
| } | |
| } | |
| if (iree_any_bit_set(params->usage, IREE_HAL_BUFFER_USAGE_DISPATCH_STORAGE)) { | |
| usage_flags |= WGPUBufferUsage_Storage; | |
| } | |
| if (iree_any_bit_set(params->usage, | |
| IREE_HAL_BUFFER_USAGE_DISPATCH_UNIFORM_READ)) { | |
| usage_flags |= WGPUBufferUsage_Uniform; | |
| } | |
| if (iree_any_bit_set(params->usage, | |
| IREE_HAL_BUFFER_USAGE_DISPATCH_INDIRECT_PARAMS)) { | |
| usage_flags |= WGPUBufferUsage_Indirect; | |
| } | |
| const bool has_initial_data = !iree_const_byte_span_is_empty(initial_data); | |
| WGPUBufferDescriptor descriptor = { | |
| .nextInChain = NULL, | |
| .label = NULL, | |
| .usage = usage_flags, | |
| .size = allocation_size, | |
| .mappedAtCreation = has_initial_data, | |
| }; | |
| WGPUBuffer buffer_handle = wgpuDeviceCreateBuffer( | |
| iree_hal_webgpu_device_handle(allocator->device), &descriptor); | |
| if (!buffer_handle) { | |
| return iree_make_status(IREE_STATUS_RESOURCE_EXHAUSTED, | |
| "unable to allocate buffer of size %" PRIdsz, | |
| allocation_size); | |
| } | |
| // Upload the initial data into the mapped buffer. In WebGPU the only | |
| // _somewhat_ efficient path for buffer initialization is setting | |
| // mappedAtCreation and populating it before unmapping. | |
| if (has_initial_data) { | |
| IREE_TRACE_ZONE_BEGIN(z1); | |
| IREE_TRACE_ZONE_APPEND_VALUE_I64(z1, (uint64_t)initial_data.data_length); | |
| void* mapped_ptr = | |
| wgpuBufferGetMappedRange(buffer_handle, 0, initial_data.data_length); | |
| memcpy(mapped_ptr, initial_data.data, initial_data.data_length); | |
| wgpuBufferUnmap(buffer_handle); | |
| IREE_TRACE_ZONE_END(z1); | |
| } | |
| iree_status_t status = iree_hal_webgpu_buffer_wrap( | |
| allocator->device, base_allocator, params->type, params->access, | |
| params->usage, allocation_size, | |
| /*byte_offset=*/0, | |
| /*byte_length=*/allocation_size, buffer_handle, allocator->host_allocator, | |
| out_buffer); | |
| if (iree_status_is_ok(status)) { | |
| IREE_STATISTICS(iree_hal_allocator_statistics_record_alloc( | |
| &allocator->statistics, params->type, allocation_size)); | |
| } else { | |
| wgpuBufferDestroy(buffer_handle); | |
| } | |
| return status; | |
| } | |
| static void iree_hal_webgpu_simple_allocator_deallocate_buffer( | |
| iree_hal_allocator_t* IREE_RESTRICT base_allocator, | |
| iree_hal_buffer_t* IREE_RESTRICT base_buffer) { | |
| iree_hal_webgpu_simple_allocator_t* allocator = | |
| iree_hal_webgpu_simple_allocator_cast(base_allocator); | |
| IREE_STATISTICS(iree_hal_allocator_statistics_record_free( | |
| &allocator->statistics, iree_hal_buffer_memory_type(base_buffer), | |
| iree_hal_buffer_allocation_size(base_buffer))); | |
| iree_hal_buffer_destroy(base_buffer); | |
| } | |
| static iree_status_t iree_hal_webgpu_allocator_import_buffer( | |
| iree_hal_allocator_t* IREE_RESTRICT base_allocator, | |
| const iree_hal_buffer_params_t* IREE_RESTRICT params, | |
| iree_hal_external_buffer_t* IREE_RESTRICT external_buffer, | |
| iree_hal_buffer_release_callback_t release_callback, | |
| iree_hal_buffer_t** IREE_RESTRICT out_buffer) { | |
| return iree_make_status(IREE_STATUS_UNAVAILABLE, | |
| "importing from external buffers not supported"); | |
| } | |
| static iree_status_t iree_hal_webgpu_allocator_export_buffer( | |
| iree_hal_allocator_t* IREE_RESTRICT base_allocator, | |
| iree_hal_buffer_t* IREE_RESTRICT buffer, | |
| iree_hal_external_buffer_type_t requested_type, | |
| iree_hal_external_buffer_flags_t requested_flags, | |
| iree_hal_external_buffer_t* IREE_RESTRICT out_external_buffer) { | |
| return iree_make_status(IREE_STATUS_UNAVAILABLE, | |
| "exporting to external buffers not supported"); | |
| } | |
| const iree_hal_allocator_vtable_t iree_hal_webgpu_simple_allocator_vtable = { | |
| .destroy = iree_hal_webgpu_simple_allocator_destroy, | |
| .host_allocator = iree_hal_webgpu_simple_allocator_host_allocator, | |
| .trim = iree_hal_webgpu_simple_allocator_trim, | |
| .query_statistics = iree_hal_webgpu_simple_allocator_query_statistics, | |
| .query_buffer_compatibility = | |
| iree_hal_webgpu_simple_allocator_query_buffer_compatibility, | |
| .allocate_buffer = iree_hal_webgpu_simple_allocator_allocate_buffer, | |
| .deallocate_buffer = iree_hal_webgpu_simple_allocator_deallocate_buffer, | |
| .import_buffer = iree_hal_webgpu_allocator_import_buffer, | |
| .export_buffer = iree_hal_webgpu_allocator_export_buffer, | |
| }; |