// Copyright 2024 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 "iree/hal/drivers/null/device.h"

#include "iree/async/frontier.h"
#include "iree/async/frontier_tracker.h"
#include "iree/async/util/proactor_pool.h"
#include "iree/hal/drivers/null/allocator.h"
#include "iree/hal/drivers/null/api.h"
#include "iree/hal/drivers/null/channel.h"
#include "iree/hal/drivers/null/command_buffer.h"
#include "iree/hal/drivers/null/event.h"
#include "iree/hal/drivers/null/executable.h"
#include "iree/hal/drivers/null/executable_cache.h"
#include "iree/hal/drivers/null/semaphore.h"
#include "iree/hal/utils/file_registry.h"
#include "iree/hal/utils/file_transfer.h"
#include "iree/hal/utils/queue_emulation.h"
#include "iree/hal/utils/queue_host_call_emulation.h"

//===----------------------------------------------------------------------===//
// iree_hal_null_device_options_t
//===----------------------------------------------------------------------===//

IREE_API_EXPORT void iree_hal_null_device_options_initialize(
    iree_hal_null_device_options_t* out_options) {
  memset(out_options, 0, sizeof(*out_options));
  // TODO(null): set defaults based on compiler configuration. Flags should not
  // be used as multiple devices may be configured within the process or the
  // hosting application may be authored in python/etc that does not use a flags
  // mechanism accessible here.
}

static iree_status_t iree_hal_null_device_options_verify(
    const iree_hal_null_device_options_t* options) {
  // TODO(null): verify that the parameters are within expected ranges and any
  // requested features are supported.
  return iree_ok_status();
}

//===----------------------------------------------------------------------===//
// iree_hal_null_device_t
//===----------------------------------------------------------------------===//

typedef struct iree_hal_null_device_t {
  iree_hal_resource_t resource;
  iree_string_view_t identifier;

  iree_allocator_t host_allocator;
  iree_hal_allocator_t* device_allocator;

  // Proactor pool for async I/O. Retained for the lifetime of the device to
  // ensure proactor threads outlive all device resources (semaphores, etc.).
  iree_async_proactor_pool_t* proactor_pool;

  // Proactor selected from the pool for this device's async I/O operations.
  // Borrowed from the pool — valid as long as the pool is retained.
  iree_async_proactor_t* proactor;

  // Shared frontier tracker for cross-device causal ordering. Retained after
  // topology assignment and released during device destruction.
  iree_async_frontier_tracker_t* frontier_tracker;

  // This device's axis and monotonic epoch counter for frontier tracking.
  // The null driver has no real submit path; these are plumbing-only.
  iree_async_axis_t axis;
  iree_atomic_int64_t epoch;

  // Optional provider used for creating/configuring collective channels.
  iree_hal_channel_provider_t* channel_provider;

  // Topology information if this device is part of a multi-device topology.
  iree_hal_device_topology_info_t topology_info;

  // + trailing identifier string storage
} iree_hal_null_device_t;

static const iree_hal_device_vtable_t iree_hal_null_device_vtable;

static iree_hal_null_device_t* iree_hal_null_device_cast(
    iree_hal_device_t* base_value) {
  IREE_HAL_ASSERT_TYPE(base_value, &iree_hal_null_device_vtable);
  return (iree_hal_null_device_t*)base_value;
}

iree_status_t iree_hal_null_device_create(
    iree_string_view_t identifier,
    const iree_hal_null_device_options_t* options,
    const iree_hal_device_create_params_t* create_params,
    iree_allocator_t host_allocator, iree_hal_device_t** out_device) {
  IREE_ASSERT_ARGUMENT(options);
  IREE_ASSERT_ARGUMENT(create_params);
  IREE_ASSERT_ARGUMENT(create_params->proactor_pool);
  IREE_ASSERT_ARGUMENT(out_device);
  IREE_TRACE_ZONE_BEGIN(z0);
  *out_device = NULL;

  // Verify the parameters prior to creating resources.
  IREE_RETURN_AND_END_ZONE_IF_ERROR(
      z0, iree_hal_null_device_options_verify(options));

  iree_hal_null_device_t* device = NULL;
  iree_host_size_t total_size = sizeof(*device) + identifier.size;
  IREE_RETURN_AND_END_ZONE_IF_ERROR(
      z0, iree_allocator_malloc(host_allocator, total_size, (void**)&device));
  iree_hal_resource_initialize(&iree_hal_null_device_vtable, &device->resource);
  iree_string_view_append_to_buffer(
      identifier, &device->identifier,
      (char*)device + total_size - identifier.size);
  device->host_allocator = host_allocator;

  // Retain the proactor pool and acquire a proactor for this device.
  device->proactor_pool = create_params->proactor_pool;
  iree_async_proactor_pool_retain(device->proactor_pool);
  iree_atomic_store(&device->epoch, 0, iree_memory_order_relaxed);
  iree_status_t status =
      iree_async_proactor_pool_get(device->proactor_pool, 0, &device->proactor);

  // TODO(null): pass device handles and pool configuration to the allocator.
  // Some implementations may share allocators across multiple devices created
  // from the same driver.
  if (iree_status_is_ok(status)) {
    status = iree_hal_null_allocator_create(host_allocator,
                                            &device->device_allocator);
  }

  if (iree_status_is_ok(status)) {
    *out_device = (iree_hal_device_t*)device;
  } else {
    iree_hal_device_release((iree_hal_device_t*)device);
  }
  IREE_TRACE_ZONE_END(z0);
  return status;
}

static void iree_hal_null_device_clear_topology_info(
    iree_hal_null_device_t* device) {
  if (device->frontier_tracker) {
    iree_async_frontier_tracker_retire_axis(
        device->frontier_tracker, device->axis,
        iree_status_from_code(IREE_STATUS_CANCELLED));
    iree_async_frontier_tracker_release(device->frontier_tracker);
    device->frontier_tracker = NULL;
    device->axis = 0;
  }
  memset(&device->topology_info, 0, sizeof(device->topology_info));
}

static void iree_hal_null_device_destroy(iree_hal_device_t* base_device) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);
  iree_allocator_t host_allocator = iree_hal_device_host_allocator(base_device);
  IREE_TRACE_ZONE_BEGIN(z0);

  // TODO(null): release all implementation resources here. It's expected that
  // this is only called once all outstanding resources created with this device
  // have been released by the application and no work is outstanding. If the
  // implementation performs internal async operations those should be shutdown
  // and joined first.

  iree_hal_null_device_clear_topology_info(device);
  iree_hal_allocator_release(device->device_allocator);
  iree_hal_channel_provider_release(device->channel_provider);
  iree_async_proactor_pool_release(device->proactor_pool);

  iree_allocator_free(host_allocator, device);

  IREE_TRACE_ZONE_END(z0);
}

static iree_string_view_t iree_hal_null_device_id(
    iree_hal_device_t* base_device) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);
  return device->identifier;
}

static iree_allocator_t iree_hal_null_device_host_allocator(
    iree_hal_device_t* base_device) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);
  return device->host_allocator;
}

static iree_hal_allocator_t* iree_hal_null_device_allocator(
    iree_hal_device_t* base_device) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);
  return device->device_allocator;
}

static void iree_hal_null_replace_device_allocator(
    iree_hal_device_t* base_device, iree_hal_allocator_t* new_allocator) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);
  iree_hal_allocator_retain(new_allocator);
  iree_hal_allocator_release(device->device_allocator);
  device->device_allocator = new_allocator;
}

static void iree_hal_null_replace_channel_provider(
    iree_hal_device_t* base_device, iree_hal_channel_provider_t* new_provider) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);
  iree_hal_channel_provider_retain(new_provider);
  iree_hal_channel_provider_release(device->channel_provider);
  device->channel_provider = new_provider;
}

static iree_status_t iree_hal_null_device_trim(iree_hal_device_t* base_device) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);

  // TODO(null): if the device has any cached resources that can be safely
  // dropped here (unused pools/etc). This is usually called in low-memory
  // situations or when the HAL device will not be used for awhile (device
  // entering sleep mode or a low power state, etc).

  IREE_RETURN_IF_ERROR(iree_hal_allocator_trim(device->device_allocator));

  return iree_ok_status();
}

static iree_status_t iree_hal_null_device_query_i64(
    iree_hal_device_t* base_device, iree_string_view_t category,
    iree_string_view_t key, int64_t* out_value) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);
  *out_value = 0;

  // TODO(null): implement additional queries. These are stubs for common ones
  // as used by the compiler. Targets may have their own, though, and connect
  // with them by emitting `hal.device.query` ops in programs or calling the
  // query method at runtime via the HAL API.

  if (iree_string_view_equal(category, IREE_SV("hal.device.id"))) {
    // NOTE: this is a fuzzy match and can allow a program to work with multiple
    // device implementations.
    *out_value =
        iree_string_view_match_pattern(device->identifier, key) ? 1 : 0;
    return iree_ok_status();
  }

  if (iree_string_view_equal(category, IREE_SV("hal.executable.format"))) {
    // NOTE: this is a fuzzy match and can allow multiple formats to be used
    // (this should return 1 for any format supported).
    // TODO(null): match a format and return true.
    *out_value = 0;
    return iree_ok_status();
  }

  // TODO(null): return basic queries for concurrency to allow programs to
  // estimate potential utilization.
  if (iree_string_view_equal(category, IREE_SV("hal.device"))) {
    if (iree_string_view_equal(key, IREE_SV("concurrency"))) {
      *out_value = 1;
      return iree_ok_status();
    }
  } else if (iree_string_view_equal(category, IREE_SV("hal.dispatch"))) {
    if (iree_string_view_equal(key, IREE_SV("concurrency"))) {
      *out_value = 1;
      return iree_ok_status();
    }
  }

  return iree_make_status(
      IREE_STATUS_NOT_FOUND,
      "unknown device configuration key value '%.*s :: %.*s'",
      (int)category.size, category.data, (int)key.size, key.data);
}

static iree_status_t iree_hal_null_device_query_capabilities(
    iree_hal_device_t* base_device,
    iree_hal_device_capabilities_t* out_capabilities) {
  // TODO(null): populate capabilities based on device features.
  // For the null driver, we return a zeroed struct indicating no special
  // hardware capabilities. Implementations should populate this with:
  // - physical_device_uuid: A unique identifier for the physical device
  // - flags: Capability bits (timeline semaphores, unified memory, etc.)
  // - semaphore/buffer import/export types: External handle support
  // - numa_node: NUMA node assignment for the device
  // - driver_device_handle: Opaque handle to the underlying device
  memset(out_capabilities, 0, sizeof(*out_capabilities));
  return iree_ok_status();
}

static const iree_hal_device_topology_info_t*
iree_hal_null_device_topology_info(iree_hal_device_t* base_device) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);
  // TODO(null): return topology info if device is part of a multi-device setup.
  // The topology_info field should be populated during device creation or when
  // the device joins a topology. For standalone devices, the zeroed struct
  // indicates the device is not part of a topology.
  // The returned pointer's lifetime matches the device.
  return &device->topology_info;
}

static iree_status_t iree_hal_null_device_refine_topology_edge(
    iree_hal_device_t* src_device, iree_hal_device_t* dst_device,
    iree_hal_topology_edge_t* edge) {
  // TODO(null): refine topology edge based on device-specific information.
  // This is only called for same-driver device pairs during topology
  // construction. Implementations can query device-specific properties to:
  // - Upgrade link class (e.g., detect high-speed interconnects)
  // - Set precise bandwidth/latency costs
  // - Enable direct memory access modes (ALIAS instead of COPY)
  // - Adjust coherency flags based on actual hardware capabilities
  // For the null driver, we have no hardware-specific refinement.
  (void)src_device;
  (void)dst_device;
  (void)edge;
  return iree_ok_status();
}

static iree_status_t iree_hal_null_device_assign_topology_info(
    iree_hal_device_t* base_device,
    const iree_hal_device_topology_info_t* topology_info) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);
  if (!topology_info) {
    iree_hal_null_device_clear_topology_info(device);
    return iree_ok_status();
  }
  iree_async_frontier_tracker_t* frontier_tracker =
      topology_info->frontier.tracker;
  iree_async_axis_t axis = topology_info->frontier.base_axis;
  IREE_RETURN_IF_ERROR(iree_async_frontier_tracker_register_axis(
      frontier_tracker, axis, /*semaphore=*/NULL));
  device->topology_info = *topology_info;
  device->frontier_tracker = frontier_tracker;
  device->axis = axis;
  iree_async_frontier_tracker_retain(device->frontier_tracker);
  return iree_ok_status();
}

static iree_status_t iree_hal_null_device_create_channel(
    iree_hal_device_t* base_device, iree_hal_queue_affinity_t queue_affinity,
    iree_hal_channel_params_t params, iree_hal_channel_t** out_channel) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);

  // TODO(null): pass any additional resources required to create the channel.
  // The device->channel_provider can be used to get default rank/count,
  // exchange IDs, etc as needed.
  (void)device;

  return iree_hal_null_channel_create(
      params, iree_hal_device_host_allocator(base_device), out_channel);
}

static iree_status_t iree_hal_null_device_create_command_buffer(
    iree_hal_device_t* base_device, iree_hal_command_buffer_mode_t mode,
    iree_hal_command_category_t command_categories,
    iree_hal_queue_affinity_t queue_affinity, iree_host_size_t binding_capacity,
    iree_hal_command_buffer_t** out_command_buffer) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);
  // TODO(null): pass any additional resources required to create the command
  // buffer. The implementation could pool command buffers here.
  return iree_hal_null_command_buffer_create(
      iree_hal_device_allocator(base_device), mode, command_categories,
      queue_affinity, binding_capacity, device->host_allocator,
      out_command_buffer);
}

static iree_status_t iree_hal_null_device_create_event(
    iree_hal_device_t* base_device, iree_hal_queue_affinity_t queue_affinity,
    iree_hal_event_flags_t flags, iree_hal_event_t** out_event) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);

  // TODO(null): pass any additional resources required to create the event.
  // The implementation could pool events here.
  (void)device;

  return iree_hal_null_event_create(queue_affinity, flags,
                                    iree_hal_device_host_allocator(base_device),
                                    out_event);
}

static iree_status_t iree_hal_null_device_create_executable_cache(
    iree_hal_device_t* base_device, iree_string_view_t identifier,
    iree_hal_executable_cache_t** out_executable_cache) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);

  // TODO(null): pass any additional resources required during executable
  // creation or cache management.
  (void)device;

  return iree_hal_null_executable_cache_create(
      identifier, iree_hal_device_host_allocator(base_device),
      out_executable_cache);
}

static iree_status_t iree_hal_null_device_import_file(
    iree_hal_device_t* base_device, iree_hal_queue_affinity_t queue_affinity,
    iree_hal_memory_access_t access, iree_io_file_handle_t* handle,
    iree_hal_external_file_flags_t flags, iree_hal_file_t** out_file) {
  // TODO(null): if the implementation supports native file operations
  // definitely prefer that. The emulated file I/O present here as a default is
  // inefficient. The queue affinity specifies which queues may access the file
  // via read and write queue operations.
  return iree_hal_file_from_handle(
      /*device_allocator=*/NULL, queue_affinity, access, handle,
      /*proactor=*/NULL, iree_hal_device_host_allocator(base_device), out_file);
}

static iree_status_t iree_hal_null_device_create_semaphore(
    iree_hal_device_t* base_device, iree_hal_queue_affinity_t queue_affinity,
    uint64_t initial_value, iree_hal_semaphore_flags_t flags,
    iree_hal_semaphore_t** out_semaphore) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);

  return iree_hal_null_semaphore_create(device->proactor, queue_affinity,
                                        initial_value, flags,
                                        device->host_allocator, out_semaphore);
}

static iree_hal_semaphore_compatibility_t
iree_hal_null_device_query_semaphore_compatibility(
    iree_hal_device_t* base_device, iree_hal_semaphore_t* semaphore) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);

  // TODO(null): return the appropriate bits for the provided semaphore
  // indicating how it may be used with this device. The semaphore may have been
  // created or imported on this device or any other device from the same
  // driver. Certain implementations may allow semaphores from other drivers to
  // be used and those can be checked here (though the API to do this isn't
  // implemented yet).
  (void)device;
  iree_hal_semaphore_compatibility_t compatibility =
      IREE_HAL_SEMAPHORE_COMPATIBILITY_NONE;

  return compatibility;
}

static iree_status_t iree_hal_null_device_query_queue_pool_backend(
    iree_hal_device_t* base_device, iree_hal_queue_affinity_t queue_affinity,
    iree_hal_queue_pool_backend_t* out_backend) {
  return iree_make_status(IREE_STATUS_UNIMPLEMENTED,
                          "queue pool backend not implemented");
}

static iree_status_t iree_hal_null_device_queue_alloca(
    iree_hal_device_t* base_device, iree_hal_queue_affinity_t queue_affinity,
    const iree_hal_semaphore_list_t wait_semaphore_list,
    const iree_hal_semaphore_list_t signal_semaphore_list,
    iree_hal_pool_t* pool, iree_hal_buffer_params_t params,
    iree_device_size_t allocation_size, iree_hal_alloca_flags_t flags,
    iree_hal_buffer_t** IREE_RESTRICT out_buffer) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);

  // TODO(null): perform an allocation of a transient buffer in queue order.
  // The allocation may be used on any queue set in the provided queue affinity.
  // Deallocation via queue_dealloca is preferred but users are allowed to
  // deallocate the buffer on the host via iree_hal_buffer_release even if they
  // allocated it with queue_alloca.
  (void)device;
  iree_status_t status = iree_make_status(IREE_STATUS_UNIMPLEMENTED,
                                          "queue alloca not implemented");

  return status;
}

static iree_status_t iree_hal_null_device_queue_dealloca(
    iree_hal_device_t* base_device, iree_hal_queue_affinity_t queue_affinity,
    const iree_hal_semaphore_list_t wait_semaphore_list,
    const iree_hal_semaphore_list_t signal_semaphore_list,
    iree_hal_buffer_t* buffer, iree_hal_dealloca_flags_t flags) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);

  // TODO(null): perform a deallocation of the transient buffer in queue order.
  // Only buffers allocated with queue_alloca on the same device will be passed.
  // Note that different queues on the same device may have allocated the buffer
  // and if the same queue must deallocate it the implementation will need to
  // track that on the buffer. The user is allowed to deallocate the buffer on
  // the host via iree_hal_buffer_release even if they allocated it with
  // queue_alloca.
  (void)device;
  iree_status_t status = iree_make_status(IREE_STATUS_UNIMPLEMENTED,
                                          "queue dealloca not implemented");

  return status;
}

static iree_status_t iree_hal_null_device_queue_fill(
    iree_hal_device_t* base_device, iree_hal_queue_affinity_t queue_affinity,
    const iree_hal_semaphore_list_t wait_semaphore_list,
    const iree_hal_semaphore_list_t signal_semaphore_list,
    iree_hal_buffer_t* target_buffer, iree_device_size_t target_offset,
    iree_device_size_t length, const void* pattern,
    iree_host_size_t pattern_length, iree_hal_fill_flags_t flags) {
  // TODO(null): if a native queue fill operation is available use that instead.
  // The emulated fill creates a command buffer and executes it and it's best if
  // the extra recording/upload/allocation time can be avoided.
  return iree_hal_device_queue_emulated_fill(
      base_device, queue_affinity, wait_semaphore_list, signal_semaphore_list,
      target_buffer, target_offset, length, pattern, pattern_length, flags);
}

static iree_status_t iree_hal_null_device_queue_update(
    iree_hal_device_t* base_device, iree_hal_queue_affinity_t queue_affinity,
    const iree_hal_semaphore_list_t wait_semaphore_list,
    const iree_hal_semaphore_list_t signal_semaphore_list,
    const void* source_buffer, iree_host_size_t source_offset,
    iree_hal_buffer_t* target_buffer, iree_device_size_t target_offset,
    iree_device_size_t length, iree_hal_update_flags_t flags) {
  // TODO(null): if a native queue update operation is available use that
  // instead. The emulated update creates a command buffer and executes it and
  // it's best if the extra recording/upload/allocation time can be avoided.
  // Since command buffers have a limited capacity for embedded data the
  // emulated version may need to allocate buffers, split the update into
  // multiple commands, or commit other sins a native implementation would be
  // able to avoid.
  return iree_hal_device_queue_emulated_update(
      base_device, queue_affinity, wait_semaphore_list, signal_semaphore_list,
      source_buffer, source_offset, target_buffer, target_offset, length,
      flags);
}

static iree_status_t iree_hal_null_device_queue_copy(
    iree_hal_device_t* base_device, iree_hal_queue_affinity_t queue_affinity,
    const iree_hal_semaphore_list_t wait_semaphore_list,
    const iree_hal_semaphore_list_t signal_semaphore_list,
    iree_hal_buffer_t* source_buffer, iree_device_size_t source_offset,
    iree_hal_buffer_t* target_buffer, iree_device_size_t target_offset,
    iree_device_size_t length, iree_hal_copy_flags_t flags) {
  // TODO(null): if a native queue copy operation is available use that instead.
  // The emulated copy creates a command buffer and executes it and it's best if
  // the extra recording/upload/allocation time can be avoided.
  return iree_hal_device_queue_emulated_copy(
      base_device, queue_affinity, wait_semaphore_list, signal_semaphore_list,
      source_buffer, source_offset, target_buffer, target_offset, length,
      flags);
}

static iree_status_t iree_hal_null_device_queue_read(
    iree_hal_device_t* base_device, iree_hal_queue_affinity_t queue_affinity,
    const iree_hal_semaphore_list_t wait_semaphore_list,
    const iree_hal_semaphore_list_t signal_semaphore_list,
    iree_hal_file_t* source_file, uint64_t source_offset,
    iree_hal_buffer_t* target_buffer, iree_device_size_t target_offset,
    iree_device_size_t length, iree_hal_read_flags_t flags) {
  // TODO(null): if native support for file operations are available then
  // definitely prefer those over the emulated implementation provided here by
  // default. The implementation performs allocations, creates semaphores, and
  // submits command buffers with host-device blocking behavior.

  iree_hal_file_transfer_options_t options = {
      .chunk_count = IREE_HAL_FILE_TRANSFER_CHUNK_COUNT_DEFAULT,
      .chunk_size = IREE_HAL_FILE_TRANSFER_CHUNK_SIZE_DEFAULT,
  };
  return iree_hal_device_queue_read_streaming(
      base_device, queue_affinity, wait_semaphore_list, signal_semaphore_list,
      source_file, source_offset, target_buffer, target_offset, length, flags,
      options);
}

static iree_status_t iree_hal_null_device_queue_write(
    iree_hal_device_t* base_device, iree_hal_queue_affinity_t queue_affinity,
    const iree_hal_semaphore_list_t wait_semaphore_list,
    const iree_hal_semaphore_list_t signal_semaphore_list,
    iree_hal_buffer_t* source_buffer, iree_device_size_t source_offset,
    iree_hal_file_t* target_file, uint64_t target_offset,
    iree_device_size_t length, iree_hal_write_flags_t flags) {
  // TODO(null): if native support for file operations are available then
  // definitely prefer those over the emulated implementation provided here by
  // default. The implementation performs allocations, creates semaphores, and
  // submits command buffers with host-device blocking behavior.

  iree_hal_file_transfer_options_t options = {
      .chunk_count = IREE_HAL_FILE_TRANSFER_CHUNK_COUNT_DEFAULT,
      .chunk_size = IREE_HAL_FILE_TRANSFER_CHUNK_SIZE_DEFAULT,
  };
  return iree_hal_device_queue_write_streaming(
      base_device, queue_affinity, wait_semaphore_list, signal_semaphore_list,
      source_buffer, source_offset, target_file, target_offset, length, flags,
      options);
}

static iree_status_t iree_hal_null_device_queue_host_call(
    iree_hal_device_t* base_device, iree_hal_queue_affinity_t queue_affinity,
    const iree_hal_semaphore_list_t wait_semaphore_list,
    const iree_hal_semaphore_list_t signal_semaphore_list,
    iree_hal_host_call_t call, const uint64_t args[4],
    iree_hal_host_call_flags_t flags) {
  // TODO(null): if a native queue host call operation is available use that
  // instead. The emulated host call is horrendous and creates a new thread for
  // every requested host call. Even if native host call support is not
  // available an implementation should do _anything_ better than launching a
  // thread per call (polling threads, worker pools, etc).
  return iree_hal_device_queue_emulated_host_call(
      base_device, queue_affinity, wait_semaphore_list, signal_semaphore_list,
      call, args, flags);
}

static iree_status_t iree_hal_null_device_queue_dispatch(
    iree_hal_device_t* base_device, iree_hal_queue_affinity_t queue_affinity,
    const iree_hal_semaphore_list_t wait_semaphore_list,
    const iree_hal_semaphore_list_t signal_semaphore_list,
    iree_hal_executable_t* executable,
    iree_hal_executable_function_t export_ordinal,
    const iree_hal_dispatch_config_t config, iree_const_byte_span_t constants,
    const iree_hal_buffer_ref_list_t bindings,
    iree_hal_dispatch_flags_t flags) {
  // TODO(null): if a native queue dispatch operation is available use that
  // instead. The emulated dispatch creates a command buffer and executes it and
  // it's best if the extra recording/upload/allocation time can be avoided.
  return iree_hal_device_queue_emulated_dispatch(
      base_device, queue_affinity, wait_semaphore_list, signal_semaphore_list,
      executable, export_ordinal, config, constants, bindings, flags);
}

static iree_status_t iree_hal_null_device_queue_execute(
    iree_hal_device_t* base_device, iree_hal_queue_affinity_t queue_affinity,
    const iree_hal_semaphore_list_t wait_semaphore_list,
    const iree_hal_semaphore_list_t signal_semaphore_list,
    iree_hal_command_buffer_t* command_buffer,
    iree_hal_buffer_binding_table_t binding_table,
    iree_hal_execute_flags_t flags) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);

  // TODO(null): implement a wait, execute, and signal queue operation. The
  // queue affinity can be used to determine which top-level execution resources
  // are to be used when executing and it can be assumed that all resources
  // required for execution are accessible on those queues. If more than one
  // queue is specified the implementation may use any it prefers from the set.

  // TODO(null): an optional binding table is provided for indirect command
  // buffers (those who have a binding_capacity > 0). The binding table must be
  // captured by the implementation as they may be mutated or freed by the
  // caller immediately after this call returns.

  // TODO(null): do this async - callers may be submitting work to multiple
  // devices or queues on the same device from the same thread and blocking here
  // will prevent both concurrency and pipelining.

  (void)device;
  iree_status_t status = iree_make_status(IREE_STATUS_UNIMPLEMENTED,
                                          "queue execute not implemented");

  return status;
}

static iree_status_t iree_hal_null_device_queue_flush(
    iree_hal_device_t* base_device, iree_hal_queue_affinity_t queue_affinity) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);

  // TODO(null): though buffering queue operations is not recommended if any
  // buffering has been performed it must be flushed here. Callers may be
  // indicating that they are about to suspend themselves waiting for submitted
  // work to complete.
  (void)device;
  iree_status_t status = iree_make_status(IREE_STATUS_UNIMPLEMENTED,
                                          "queue flush not implemented");

  return status;
}

static iree_status_t iree_hal_null_device_profiling_begin(
    iree_hal_device_t* base_device,
    const iree_hal_device_profiling_options_t* options) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);

  // TODO(null): set implementation-defined device or global profiling data
  // families. This will be paired with a profiling_end call at some point in
  // the future. Hosting applications may periodically call profiling_flush.
  (void)device;
  iree_status_t status = iree_make_status(IREE_STATUS_UNIMPLEMENTED,
                                          "device profiling not implemented");

  return status;
}

static iree_status_t iree_hal_null_device_profiling_flush(
    iree_hal_device_t* base_device) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);

  // TODO(null): flush if needed. May be no-op. Any accumulated profiling
  // information should be carried across the flush but the event can be used to
  // reclaim resources or perform other expensive bookkeeping. Benchmarks, for
  // example, are expected to call this periodically with their timing
  // suspended.
  (void)device;
  iree_status_t status = iree_make_status(IREE_STATUS_UNIMPLEMENTED,
                                          "device profiling not implemented");

  return status;
}

static iree_status_t iree_hal_null_device_profiling_end(
    iree_hal_device_t* base_device) {
  iree_hal_null_device_t* device = iree_hal_null_device_cast(base_device);

  // TODO(null): unset whatever profiling_begin set, if anything. May be no-op.
  (void)device;
  iree_status_t status = iree_make_status(IREE_STATUS_UNIMPLEMENTED,
                                          "device profiling not implemented");

  return status;
}

static iree_status_t iree_hal_null_device_external_capture_begin(
    iree_hal_device_t* base_device,
    const iree_hal_device_external_capture_options_t* options) {
  (void)base_device;
  (void)options;
  return iree_make_status(IREE_STATUS_UNIMPLEMENTED,
                          "null external capture not implemented");
}

static iree_status_t iree_hal_null_device_external_capture_end(
    iree_hal_device_t* base_device) {
  (void)base_device;
  return iree_make_status(IREE_STATUS_UNIMPLEMENTED,
                          "null external capture not implemented");
}

static const iree_hal_device_vtable_t iree_hal_null_device_vtable = {
    .destroy = iree_hal_null_device_destroy,
    .id = iree_hal_null_device_id,
    .host_allocator = iree_hal_null_device_host_allocator,
    .device_allocator = iree_hal_null_device_allocator,
    .replace_device_allocator = iree_hal_null_replace_device_allocator,
    .replace_channel_provider = iree_hal_null_replace_channel_provider,
    .trim = iree_hal_null_device_trim,
    .query_i64 = iree_hal_null_device_query_i64,
    .query_capabilities = iree_hal_null_device_query_capabilities,
    .topology_info = iree_hal_null_device_topology_info,
    .refine_topology_edge = iree_hal_null_device_refine_topology_edge,
    .assign_topology_info = iree_hal_null_device_assign_topology_info,
    .create_channel = iree_hal_null_device_create_channel,
    .create_command_buffer = iree_hal_null_device_create_command_buffer,
    .create_event = iree_hal_null_device_create_event,
    .create_executable_cache = iree_hal_null_device_create_executable_cache,
    .import_file = iree_hal_null_device_import_file,
    .create_semaphore = iree_hal_null_device_create_semaphore,
    .query_semaphore_compatibility =
        iree_hal_null_device_query_semaphore_compatibility,
    .query_queue_pool_backend = iree_hal_null_device_query_queue_pool_backend,
    .queue_alloca = iree_hal_null_device_queue_alloca,
    .queue_dealloca = iree_hal_null_device_queue_dealloca,
    .queue_fill = iree_hal_null_device_queue_fill,
    .queue_update = iree_hal_null_device_queue_update,
    .queue_copy = iree_hal_null_device_queue_copy,
    .queue_read = iree_hal_null_device_queue_read,
    .queue_write = iree_hal_null_device_queue_write,
    .queue_host_call = iree_hal_null_device_queue_host_call,
    .queue_dispatch = iree_hal_null_device_queue_dispatch,
    .queue_execute = iree_hal_null_device_queue_execute,
    .queue_flush = iree_hal_null_device_queue_flush,
    .profiling_begin = iree_hal_null_device_profiling_begin,
    .profiling_flush = iree_hal_null_device_profiling_flush,
    .profiling_end = iree_hal_null_device_profiling_end,
    .external_capture_begin = iree_hal_null_device_external_capture_begin,
    .external_capture_end = iree_hal_null_device_external_capture_end,
};
