/*
 * Copyright 2025 Valve Corporation
 * SPDX-License-Identifier: MIT
 */

#include "nir.h"

/**
 * Find the lowest common ancestor in the dominance tree.
 *
 * We reduce the LCA problem to range minimum query using the standard euler
 * tour method (see eg. Bender and Colton section 2). From there, we use the
 * simple RMQ algorithm that uses O(n log n) preprcessing time and O(1) query
 * time (Bender and Colton section 3).
 *
 * As a slight modification, we store the block index instead of the block
 * depth. We can do this because the lower tree depth is always at a lower block
 * index and we use an RMQ algorithm that doesn't rely on the -1/+1 property.
 *
 * Bender, M.A., Farach-Colton, M. (2000). The LCA Problem Revisited. In:
 *     Gonnet, G.H., Viola, A. (eds) LATIN 2000: Theoretical Informatics. LATIN
 *     2000. Lecture Notes in Computer Science, vol 1776. Springer, Berlin,
 *     Heidelberg. https://doi.org/10.1007/10719839_9
 */

static void
realloc_info(nir_function_impl *impl)
{
   struct nir_dom_lca_info *info = &impl->dom_lca_info;
   const uint32_t euler_tour_size = impl->num_blocks * 2 - 1;

   void *mem_ctx = ralloc_parent(impl);
   range_minimum_query_table_resize(&info->table, mem_ctx, euler_tour_size);
   info->block_from_idx = reralloc_array_size(mem_ctx, info->block_from_idx,
                                              sizeof(nir_block *),
                                              impl->num_blocks);
}

static uint32_t
dom_lca_representative(nir_block *block)
{
   /* The dom_pre_index is 1-indexed so we need to subtract one to match our
    * indices
    */
   return block->dom_pre_index - 1;
}

static void
generate_euler_tour(nir_function_impl *impl)
{
   uint32_t *table = impl->dom_lca_info.table.table;
   nir_block **block_from_idx = impl->dom_lca_info.block_from_idx;
   if (impl->num_blocks == 1) {
      nir_block *block = nir_start_block(impl);
      table[0] = 0;
      block_from_idx[0] = block;
      return;
   }

   /* By definition, the first row of the table contains range minimum query
    * lookups for each single-element block, meaning it is just the array that
    * we will perform RMQs on. Therefore, when generating the Euler tour, we
    * store results in the first row and are free to use the rest of the table
    * as scratch memory for the depth-first search.
    *
    * The stack contains the index of the node's next child to visit.
    */
   assert(impl->dom_lca_info.table.height >= 2);
   STATIC_ASSERT(sizeof(uint32_t) <= sizeof(nir_block *));
   uint32_t *dfs_stack = (uint32_t *)&table[impl->dom_lca_info.table.width];

   nir_block *cur_block = nir_start_block(impl);
   uint32_t *cur_stack = dfs_stack;

   bool first_visit = true;
   uint32_t i;
   for (i = 0; i < impl->dom_lca_info.table.width; i++) {
      if (cur_block == NULL) {
         /* This can happen earlier than expected if some blocks are
          * unreachable
          */
         break;
      }

      assert(cur_stack >= dfs_stack);
      table[i] = cur_block->index;

      if (first_visit) {
         /* First visit. Place it on the stack. */
         *cur_stack = 0;
         assert(i == dom_lca_representative(cur_block));
         block_from_idx[cur_block->index] = cur_block;
      }

      if (*cur_stack < cur_block->num_dom_children) {
         cur_block = cur_block->dom_children[*cur_stack];
         *cur_stack += 1;
         cur_stack += 1;
         first_visit = true;
      } else {
         assert(*cur_stack == cur_block->num_dom_children);
         cur_block = cur_block->imm_dom;
         cur_stack -= 1;
         first_visit = false;
      }
   }

   assert(cur_block == NULL);

   if (i != impl->dom_lca_info.table.width) {
      void *mem_ctx = ralloc_parent(impl);
      range_minimum_query_table_resize(&impl->dom_lca_info.table, mem_ctx, i);
   }
}

void
nir_calc_dominance_lca_impl(nir_function_impl *impl)
{
   if (impl->valid_metadata & nir_metadata_dominance_lca)
      return;

   nir_metadata_require(impl, nir_metadata_block_index |
                                 nir_metadata_dominance);

   realloc_info(impl);
   generate_euler_tour(impl);
   range_minimum_query_table_preprocess(&impl->dom_lca_info.table);
}

static nir_block *
block_return_if_reachable(nir_block *b)
{
   return (b && nir_block_is_reachable(b)) ? b : NULL;
}

static bool
is_lca(nir_block *result, nir_block *b1, nir_block *b2)
{
   if (!nir_block_dominates(result, b1) || !nir_block_dominates(result, b2))
      return false;

   for (int i = 0; i < result->num_dom_children; i++) {
      nir_block *child = result->dom_children[i];
      if (nir_block_dominates(child, b1) &&
          nir_block_dominates(child, b2))
         return false;
   }

   return true;
}

nir_block *
nir_dominance_lca(nir_block *b1, nir_block *b2)
{
   if (b1 == NULL || !nir_block_is_reachable(b1))
      return block_return_if_reachable(b2);

   if (b2 == NULL || !nir_block_is_reachable(b2))
      return block_return_if_reachable(b1);

   assert(nir_cf_node_get_function(&b1->cf_node) ==
          nir_cf_node_get_function(&b2->cf_node));

   nir_function_impl *impl = nir_cf_node_get_function(&b1->cf_node);
   assert(impl->valid_metadata & nir_metadata_dominance_lca);

   uint32_t i1 = dom_lca_representative(b1);
   uint32_t i2 = dom_lca_representative(b2);
   if (i1 > i2)
      SWAP(i1, i2);
   uint32_t index = range_minimum_query(&impl->dom_lca_info.table, i1, i2 + 1);
   nir_block *result = impl->dom_lca_info.block_from_idx[index];

   assert(is_lca(result, b1, b2));

   return result;
}
