[metal] Use MTLFence to synchronize encoders in command buffer

This is needed for `MTLHazardTrackingModeTracked` mode per
Metal documentation:

> An MTLFence synchronizes access to one or more resources across
> different passes within a command buffer. Use fences to specify
> any inter-pass resource dependencies within the same command
> buffer.
diff --git a/experimental/metal/direct_command_buffer.m b/experimental/metal/direct_command_buffer.m
index bfb6081..3a3c202 100644
--- a/experimental/metal/direct_command_buffer.m
+++ b/experimental/metal/direct_command_buffer.m
@@ -106,7 +106,18 @@
 
 static id<MTLComputeCommandEncoder> iree_hal_metal_get_or_begin_compute_encoder(
     iree_hal_metal_command_buffer_t* command_buffer) {
-  if (command_buffer->blit_encoder) iree_hal_metal_end_blit_encoder(command_buffer);
+  id<MTLFence> encoder_fence = nil;
+  if (command_buffer->blit_encoder) {
+    // We would need to use a fence to synchronize "one or more resources across different passes
+    // within a command buffer."
+    // https://developer.apple.com/documentation/metal/resource_synchronization
+    encoder_fence = [command_buffer->command_buffer.device newFence];  // +1
+    [command_buffer->command_buffer addCompletedHandler:^(id<MTLCommandBuffer> cb) {
+      [encoder_fence release];  // -1
+    }];
+    [command_buffer->blit_encoder updateFence:encoder_fence];
+    iree_hal_metal_end_blit_encoder(command_buffer);
+  }
 
   @autoreleasepool {  // Use @autoreleasepool to trigger the autorelease within encoder creation.
     if (!command_buffer->compute_encoder) {
@@ -116,12 +127,27 @@
           computeCommandEncoderWithDispatchType:command_buffer->dispatch_type] retain];  // +1
     }
   }
+
+  if (encoder_fence != nil) {
+    [command_buffer->compute_encoder waitForFence:encoder_fence];
+  }
   return command_buffer->compute_encoder;
 }
 
 static id<MTLBlitCommandEncoder> iree_hal_metal_get_or_begin_blit_encoder(
     iree_hal_metal_command_buffer_t* command_buffer) {
-  if (command_buffer->compute_encoder) iree_hal_metal_end_compute_encoder(command_buffer);
+  id<MTLFence> encoder_fence = nil;
+  if (command_buffer->compute_encoder) {
+    // We would need to use a fence to synchronize "one or more resources across different passes
+    // within a command buffer."
+    // https://developer.apple.com/documentation/metal/resource_synchronization
+    encoder_fence = [command_buffer->command_buffer.device newFence];  // +1
+    [command_buffer->command_buffer addCompletedHandler:^(id<MTLCommandBuffer> cb) {
+      [encoder_fence release];  // -1
+    }];
+    [command_buffer->compute_encoder updateFence:encoder_fence];
+    iree_hal_metal_end_compute_encoder(command_buffer);
+  }
 
   @autoreleasepool {  // Use @autoreleasepool to trigger the autorelease within encoder creation.
     if (!command_buffer->blit_encoder) {
@@ -129,6 +155,10 @@
           [[command_buffer->command_buffer blitCommandEncoder] retain];  // +1
     }
   }
+
+  if (encoder_fence != nil) {
+    [command_buffer->blit_encoder waitForFence:encoder_fence];
+  }
   return command_buffer->blit_encoder;
 }