[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);
 }