[metal] Improve error handling in command buffer create/destroy We need to check the return status of resource set allcoation and properly destroy the command buffer if any error happens.
diff --git a/experimental/metal/direct_command_buffer.m b/experimental/metal/direct_command_buffer.m index a1cf125..c504219 100644 --- a/experimental/metal/direct_command_buffer.m +++ b/experimental/metal/direct_command_buffer.m
@@ -322,6 +322,11 @@ return command_buffer->state.blit_encoder; } +// Destroys the given |base_command_buffer| itself, without decreasing refcount in the shared +// staging buffer yet. +static void iree_hal_metal_command_buffer_destroy_internal( + iree_hal_command_buffer_t* base_command_buffer); + iree_status_t iree_hal_metal_direct_command_buffer_create( iree_hal_device_t* device, iree_hal_command_buffer_mode_t mode, iree_hal_command_category_t command_categories, iree_host_size_t binding_capacity, @@ -356,48 +361,50 @@ command_buffer->staging_buffer = staging_buffer; command_buffer->host_allocator = host_allocator; iree_status_t status = iree_hal_resource_set_allocate(block_pool, &command_buffer->resource_set); - iree_hal_metal_command_segment_list_reset(&command_buffer->segments); - @autoreleasepool { // Use @autoreleasepool to trigger the autorelease within encoder creation. - // We track resource lifetime by ourselves in IREE; so just do unretained references to - // resources in Metal command buffer, which avoids overhead and gives better performance. - MTLCommandBufferDescriptor* descriptor = [MTLCommandBufferDescriptor new]; // +1 - descriptor.retainedReferences = - resource_reference_mode == IREE_HAL_METAL_COMMAND_BUFFER_RESOURCE_REFERENCE_MODE_RETAINED; - descriptor.errorOptions = MTLCommandBufferErrorOptionNone; - command_buffer->command_buffer = [[queue commandBufferWithDescriptor:descriptor] retain]; // +1 - [descriptor release]; // -1 + if (iree_status_is_ok(status)) { + iree_hal_metal_command_segment_list_reset(&command_buffer->segments); + @autoreleasepool { // Use @autoreleasepool to trigger the autorelease within encoder creation. + // We track resource lifetime by ourselves in IREE; so just do unretained references to + // resources in Metal command buffer, which avoids overhead and gives better performance. + MTLCommandBufferDescriptor* descriptor = [MTLCommandBufferDescriptor new]; // +1 + descriptor.retainedReferences = + resource_reference_mode == IREE_HAL_METAL_COMMAND_BUFFER_RESOURCE_REFERENCE_MODE_RETAINED; + descriptor.errorOptions = MTLCommandBufferErrorOptionNone; + command_buffer->command_buffer = + [[queue commandBufferWithDescriptor:descriptor] retain]; // +1 + [descriptor release]; // -1 + } + const iree_hal_metal_device_params_t* params = iree_hal_metal_device_params(device); + command_buffer->dispatch_type = + params->command_dispatch_type == IREE_HAL_METAL_COMMAND_DISPATCH_TYPE_CONCURRENT + ? MTLDispatchTypeConcurrent + : MTLDispatchTypeSerial; + command_buffer->state.compute_encoder = nil; + command_buffer->state.blit_encoder = nil; + command_buffer->state.encoder_event = [queue.device newEvent]; // +1 + command_buffer->state.next_encoder_event_value = 1; } - const iree_hal_metal_device_params_t* params = iree_hal_metal_device_params(device); - command_buffer->dispatch_type = - params->command_dispatch_type == IREE_HAL_METAL_COMMAND_DISPATCH_TYPE_CONCURRENT - ? MTLDispatchTypeConcurrent - : MTLDispatchTypeSerial; - command_buffer->state.compute_encoder = nil; - command_buffer->state.blit_encoder = nil; - command_buffer->state.encoder_event = [queue.device newEvent]; // +1 - command_buffer->state.next_encoder_event_value = 1; - *out_command_buffer = &command_buffer->base; + if (iree_status_is_ok(status)) { + *out_command_buffer = &command_buffer->base; - // Increase command buffer refcount in the shared staging buffer. We tie this to the command - // buffer's lifetime to avoid resource leak. - iree_hal_metal_staging_buffer_increase_refcount(staging_buffer); + // Increase command buffer refcount in the shared staging buffer. We tie this to the command + // buffer's lifetime to avoid resource leak. + iree_hal_metal_staging_buffer_increase_refcount(staging_buffer); + } else { + iree_hal_metal_command_buffer_destroy_internal(&command_buffer->base); + } IREE_TRACE_ZONE_END(z0); return status; } -static void iree_hal_metal_command_buffer_destroy(iree_hal_command_buffer_t* base_command_buffer) { +static void iree_hal_metal_command_buffer_destroy_internal( + iree_hal_command_buffer_t* base_command_buffer) { iree_hal_metal_command_buffer_t* command_buffer = iree_hal_metal_command_buffer_cast(base_command_buffer); - IREE_TRACE_ZONE_BEGIN(z0); iree_hal_metal_command_buffer_reset(command_buffer); - - // Decrease command buffer refcount in the shared staging buffer, and potentially reclaim - // resources. We tie this to the command buffer's lifetime to avoid resource leak. - iree_hal_metal_staging_buffer_decrease_refcount(command_buffer->staging_buffer); - [command_buffer->state.encoder_event release]; // -1 IREE_ASSERT_EQ(command_buffer->state.compute_encoder, nil); IREE_ASSERT_EQ(command_buffer->state.blit_encoder, nil); @@ -406,6 +413,20 @@ iree_hal_resource_set_free(command_buffer->resource_set); iree_arena_deinitialize(&command_buffer->arena); iree_allocator_free(command_buffer->host_allocator, command_buffer); +} + +static void iree_hal_metal_command_buffer_destroy(iree_hal_command_buffer_t* base_command_buffer) { + iree_hal_metal_command_buffer_t* command_buffer = + iree_hal_metal_command_buffer_cast(base_command_buffer); + IREE_TRACE_ZONE_BEGIN(z0); + + // Decrease command buffer refcount in the shared staging buffer, and potentially reclaim + // resources. We tie this to the command buffer's lifetime to avoid resource leak. + if (command_buffer->staging_buffer) { + iree_hal_metal_staging_buffer_decrease_refcount(command_buffer->staging_buffer); + } + + iree_hal_metal_command_buffer_destroy_internal(base_command_buffer); IREE_TRACE_ZONE_END(z0); }