// Copyright 2022 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/utils/collective_batch.h"

//===----------------------------------------------------------------------===//
// Collective batching utility
//===----------------------------------------------------------------------===//

#define IREE_HAL_COLLECTIVE_BATCH_INITIAL_CAPACITY 16

IREE_API_EXPORT void iree_hal_collective_batch_initialize(
    iree_arena_allocator_t* arena, iree_hal_resource_set_t* resource_set,
    iree_hal_collective_batch_t* out_batch) {
  out_batch->arena = arena;
  out_batch->resource_set = resource_set;
  out_batch->capacity = 0;
  out_batch->count = 0;
  out_batch->entries = NULL;
}

IREE_API_EXPORT void iree_hal_collective_batch_deinitialize(
    iree_hal_collective_batch_t* batch) {
  // Since we are just allocating from the arena we don't need to do anything
  // but clear our pointers for debugging clarity.
  batch->capacity = 0;
  batch->count = 0;
  batch->entries = NULL;
}

IREE_API_EXPORT bool iree_hal_collective_batch_is_empty(
    const iree_hal_collective_batch_t* batch) {
  return batch->count == 0;
}

IREE_API_EXPORT void iree_hal_collective_batch_clear(
    iree_hal_collective_batch_t* batch) {
  // Reset the count to zero but keep the arena storage for reuse.
  // We could memset the contents if we wanted to make debugging easier as ASAN
  // won't be able to help us but it'd probably be better to use ASAN hooks to
  // mark the memory as invalid instead.
  batch->count = 0;
}

// Grows the storage of the |batch| by 2x by slicing off new memory from the
// arena and copying over the existing contents.
static iree_status_t iree_hal_collective_batch_grow(
    iree_hal_collective_batch_t* batch) {
  IREE_TRACE_ZONE_BEGIN(z0);

  // Calculate new capacity. Note that we start empty.
  iree_host_size_t new_capacity =
      batch->capacity == 0 ? IREE_HAL_COLLECTIVE_BATCH_INITIAL_CAPACITY
                           : batch->capacity * 2;
  IREE_TRACE_ZONE_APPEND_VALUE_I64(z0, new_capacity);

  // Allocate new storage - this may fail if the system (or block pool) is over
  // capacity.
  iree_hal_collective_batch_entry_t* new_entries = NULL;
  IREE_RETURN_AND_END_ZONE_IF_ERROR(
      z0,
      iree_arena_allocate(batch->arena, new_capacity * sizeof(*batch->entries),
                          (void**)&new_entries));

  // Copy over existing items. We let the old entry list go as it'll eventually
  // be cleaned up when the arena is reset.
  memcpy(new_entries, batch->entries, batch->count * sizeof(*batch->entries));
  batch->capacity = new_capacity;
  batch->entries = new_entries;

  IREE_TRACE_ZONE_END(z0);
  return iree_ok_status();
}

IREE_API_EXPORT iree_status_t iree_hal_collective_batch_append(
    iree_hal_collective_batch_t* batch, iree_hal_channel_t* channel,
    iree_hal_collective_op_t op, uint32_t param,
    iree_hal_buffer_binding_t send_binding,
    iree_hal_buffer_binding_t recv_binding, iree_device_size_t element_count) {
  // Grow the entry storage if required.
  if (batch->count + 1 > batch->capacity) {
    IREE_RETURN_IF_ERROR(iree_hal_collective_batch_grow(batch));
  }

  // Insert resources into the resource set to keep them live.
  iree_host_size_t resource_count = 0;
  void* resources[3] = {NULL};
  resources[resource_count++] = channel;
  if (send_binding.buffer) {
    resources[resource_count++] = send_binding.buffer;
  }
  if (recv_binding.buffer) {
    resources[resource_count++] = recv_binding.buffer;
  }
  IREE_RETURN_IF_ERROR(iree_hal_resource_set_insert(batch->resource_set,
                                                    resource_count, resources));

  // Append entry to the list.
  batch->entries[batch->count++] = (iree_hal_collective_batch_entry_t){
      .channel = channel,
      .op = op,
      .param = param,
      .send_binding = send_binding,
      .recv_binding = recv_binding,
      .element_count = element_count,
  };

  return iree_ok_status();
}
