/*
 * Copyright © 2014 Intel Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/**
 * \file opt_minmax.cpp
 *
 * Drop operands from an expression tree of only min/max operations if they
 * can be proven to not contribute to the final result.
 *
 * The algorithm is similar to alpha-beta pruning on a minmax search.
 */

#include "ir.h"
#include "ir_visitor.h"
#include "ir_rvalue_visitor.h"
#include "ir_optimization.h"
#include "ir_builder.h"
#include "program/prog_instruction.h"
#include "compiler/glsl_types.h"
#include "main/macros.h"

using namespace ir_builder;

namespace {

enum compare_components_result {
   LESS,
   LESS_OR_EQUAL,
   EQUAL,
   GREATER_OR_EQUAL,
   GREATER,
   MIXED
};

class minmax_range {
public:
   minmax_range(ir_constant *low = NULL, ir_constant *high = NULL)
   {
      this->low = low;
      this->high = high;
   }

   /* low is the lower limit of the range, high is the higher limit. NULL on
    * low means negative infinity (unlimited) and on high positive infinity
    * (unlimited). Because of the two interpretations of the value NULL,
    * arbitrary comparison between ir_constants is impossible.
    */
   ir_constant *low;
   ir_constant *high;
};

class ir_minmax_visitor : public ir_rvalue_enter_visitor {
public:
   ir_minmax_visitor()
      : progress(false)
   {
   }

   ir_rvalue *prune_expression(ir_expression *expr, minmax_range baserange);

   void handle_rvalue(ir_rvalue **rvalue);

   bool progress;
};

/*
 * Returns LESS if all vector components of `a' are strictly lower than of `b',
 * GREATER if all vector components of `a' are strictly greater than of `b',
 * MIXED if some vector components of `a' are strictly lower than of `b' while
 * others are strictly greater, or EQUAL otherwise.
 */
static enum compare_components_result
compare_components(ir_constant *a, ir_constant *b)
{
   assert(a != NULL);
   assert(b != NULL);

   assert(a->type->base_type == b->type->base_type);

   unsigned a_inc = a->type->is_scalar() ? 0 : 1;
   unsigned b_inc = b->type->is_scalar() ? 0 : 1;
   unsigned components = MAX2(a->type->components(), b->type->components());

   bool foundless = false;
   bool foundgreater = false;
   bool foundequal = false;

   for (unsigned i = 0, c0 = 0, c1 = 0;
        i < components;
        c0 += a_inc, c1 += b_inc, ++i) {
      switch (a->type->base_type) {
      case GLSL_TYPE_UINT:
         if (a->value.u[c0] < b->value.u[c1])
            foundless = true;
         else if (a->value.u[c0] > b->value.u[c1])
            foundgreater = true;
         else
            foundequal = true;
         break;
      case GLSL_TYPE_INT:
         if (a->value.i[c0] < b->value.i[c1])
            foundless = true;
         else if (a->value.i[c0] > b->value.i[c1])
            foundgreater = true;
         else
            foundequal = true;
         break;
      case GLSL_TYPE_FLOAT:
         if (a->value.f[c0] < b->value.f[c1])
            foundless = true;
         else if (a->value.f[c0] > b->value.f[c1])
            foundgreater = true;
         else
            foundequal = true;
         break;
      case GLSL_TYPE_DOUBLE:
         if (a->value.d[c0] < b->value.d[c1])
            foundless = true;
         else if (a->value.d[c0] > b->value.d[c1])
            foundgreater = true;
         else
            foundequal = true;
         break;
      default:
         unreachable("not reached");
      }
   }

   if (foundless && foundgreater) {
      /* Some components are strictly lower, others are strictly greater */
      return MIXED;
   }

   if (foundequal) {
       /* It is not mixed, but it is not strictly lower or greater */
      if (foundless)
         return LESS_OR_EQUAL;
      if (foundgreater)
         return GREATER_OR_EQUAL;
      return EQUAL;
   }

   /* All components are strictly lower or strictly greater */
   return foundless ? LESS : GREATER;
}

static ir_constant *
combine_constant(bool ismin, ir_constant *a, ir_constant *b)
{
   void *mem_ctx = ralloc_parent(a);
   ir_constant *c = a->clone(mem_ctx, NULL);
   for (unsigned i = 0; i < c->type->components(); i++) {
      switch (c->type->base_type) {
      case GLSL_TYPE_UINT:
         if ((ismin && b->value.u[i] < c->value.u[i]) ||
             (!ismin && b->value.u[i] > c->value.u[i]))
            c->value.u[i] = b->value.u[i];
         break;
      case GLSL_TYPE_INT:
         if ((ismin && b->value.i[i] < c->value.i[i]) ||
             (!ismin && b->value.i[i] > c->value.i[i]))
            c->value.i[i] = b->value.i[i];
         break;
      case GLSL_TYPE_FLOAT:
         if ((ismin && b->value.f[i] < c->value.f[i]) ||
             (!ismin && b->value.f[i] > c->value.f[i]))
            c->value.f[i] = b->value.f[i];
         break;
      case GLSL_TYPE_DOUBLE:
         if ((ismin && b->value.d[i] < c->value.d[i]) ||
             (!ismin && b->value.d[i] > c->value.d[i]))
            c->value.d[i] = b->value.d[i];
         break;
      default:
         assert(!"not reached");
      }
   }
   return c;
}

static ir_constant *
smaller_constant(ir_constant *a, ir_constant *b)
{
   assert(a != NULL);
   assert(b != NULL);

   enum compare_components_result ret = compare_components(a, b);
   if (ret == MIXED)
      return combine_constant(true, a, b);
   else if (ret < EQUAL)
      return a;
   else
      return b;
}

static ir_constant *
larger_constant(ir_constant *a, ir_constant *b)
{
   assert(a != NULL);
   assert(b != NULL);

   enum compare_components_result ret = compare_components(a, b);
   if (ret == MIXED)
      return combine_constant(false, a, b);
   else if (ret < EQUAL)
      return b;
   else
      return a;
}

/* Combines two ranges by doing an element-wise min() / max() depending on the
 * operation.
 */
static minmax_range
combine_range(minmax_range r0, minmax_range r1, bool ismin)
{
   minmax_range ret;

   if (!r0.low) {
      ret.low = ismin ? r0.low : r1.low;
   } else if (!r1.low) {
      ret.low = ismin ? r1.low : r0.low;
   } else {
      ret.low = ismin ? smaller_constant(r0.low, r1.low) :
         larger_constant(r0.low, r1.low);
   }

   if (!r0.high) {
      ret.high = ismin ? r1.high : r0.high;
   } else if (!r1.high) {
      ret.high = ismin ? r0.high : r1.high;
   } else {
      ret.high = ismin ? smaller_constant(r0.high, r1.high) :
         larger_constant(r0.high, r1.high);
   }

   return ret;
}

/* Returns a range so that lower limit is the larger of the two lower limits,
 * and higher limit is the smaller of the two higher limits.
 */
static minmax_range
range_intersection(minmax_range r0, minmax_range r1)
{
   minmax_range ret;

   if (!r0.low)
      ret.low = r1.low;
   else if (!r1.low)
      ret.low = r0.low;
   else
      ret.low = larger_constant(r0.low, r1.low);

   if (!r0.high)
      ret.high = r1.high;
   else if (!r1.high)
      ret.high = r0.high;
   else
      ret.high = smaller_constant(r0.high, r1.high);

   return ret;
}

static minmax_range
get_range(ir_rvalue *rval)
{
   ir_expression *expr = rval->as_expression();
   if (expr && (expr->operation == ir_binop_min ||
                expr->operation == ir_binop_max)) {
      minmax_range r0 = get_range(expr->operands[0]);
      minmax_range r1 = get_range(expr->operands[1]);
      return combine_range(r0, r1, expr->operation == ir_binop_min);
   }

   ir_constant *c = rval->as_constant();
   if (c) {
      return minmax_range(c, c);
   }

   return minmax_range();
}

/**
 * Prunes a min/max expression considering the base range of the parent
 * min/max expression.
 *
 * @param baserange the range that the parents of this min/max expression
 * in the min/max tree will clamp its value to.
 */
ir_rvalue *
ir_minmax_visitor::prune_expression(ir_expression *expr, minmax_range baserange)
{
   assert(expr->operation == ir_binop_min ||
          expr->operation == ir_binop_max);

   bool ismin = expr->operation == ir_binop_min;
   minmax_range limits[2];

   /* Recurse to get the ranges for each of the subtrees of this
    * expression. We need to do this as a separate step because we need to
    * know the ranges of each of the subtrees before we prune either one.
    * Consider something like this:
    *
    *        max
    *     /       \
    *    max     max
    *   /   \   /   \
    *  3    a   b    2
    *
    * We would like to prune away the max on the bottom-right, but to do so
    * we need to know the range of the expression on the left beforehand,
    * and there's no guarantee that we will visit either subtree in a
    * particular order.
    */
   for (unsigned i = 0; i < 2; ++i)
      limits[i] = get_range(expr->operands[i]);

   for (unsigned i = 0; i < 2; ++i) {
      bool is_redundant = false;

      enum compare_components_result cr = LESS;
      if (ismin) {
         /* If this operand will always be greater than the other one, it's
          * redundant.
          */
         if (limits[i].low && limits[1 - i].high) {
               cr = compare_components(limits[i].low, limits[1 - i].high);
            if (cr >= EQUAL && cr != MIXED)
               is_redundant = true;
         }
         /* If this operand is always greater than baserange, then even if
          * it's smaller than the other one it'll get clamped, so it's
          * redundant.
          */
         if (!is_redundant && limits[i].low && baserange.high) {
            cr = compare_components(limits[i].low, baserange.high);
            if (cr >= EQUAL && cr != MIXED)
               is_redundant = true;
         }
      } else {
         /* If this operand will always be lower than the other one, it's
          * redundant.
          */
         if (limits[i].high && limits[1 - i].low) {
            cr = compare_components(limits[i].high, limits[1 - i].low);
            if (cr <= EQUAL)
               is_redundant = true;
         }
         /* If this operand is always lower than baserange, then even if
          * it's greater than the other one it'll get clamped, so it's
          * redundant.
          */
         if (!is_redundant && limits[i].high && baserange.low) {
            cr = compare_components(limits[i].high, baserange.low);
            if (cr <= EQUAL)
               is_redundant = true;
         }
      }

      if (is_redundant) {
         progress = true;

         /* Recurse if necessary. */
         ir_expression *op_expr = expr->operands[1 - i]->as_expression();
         if (op_expr && (op_expr->operation == ir_binop_min ||
                         op_expr->operation == ir_binop_max)) {
            return prune_expression(op_expr, baserange);
         }

         return expr->operands[1 - i];
      } else if (cr == MIXED) {
         /* If we have mixed vector operands, we can try to resolve the minmax
          * expression by doing a component-wise minmax:
          *
          *             min                          min
          *           /    \                       /    \
          *         min     a       ===>        [1,1]    a
          *       /    \
          *    [1,3]   [3,1]
          *
          */
         ir_constant *a = expr->operands[0]->as_constant();
         ir_constant *b = expr->operands[1]->as_constant();
         if (a && b)
            return combine_constant(ismin, a, b);
      }
   }

   /* Now recurse to operands giving them the proper baserange. The baserange
    * to pass is the intersection of our baserange and the other operand's
    * limit with one of the ranges unlimited. If we can't compute a valid
    * intersection, we use the current baserange.
    */
   for (unsigned i = 0; i < 2; ++i) {
      ir_expression *op_expr = expr->operands[i]->as_expression();
      if (op_expr && (op_expr->operation == ir_binop_min ||
                      op_expr->operation == ir_binop_max)) {
         /* We can only compute a new baserange for this operand if we managed
          * to compute a valid range for the other operand.
          */
         if (ismin)
            limits[1 - i].low = NULL;
         else
            limits[1 - i].high = NULL;
         minmax_range base = range_intersection(limits[1 - i], baserange);
         expr->operands[i] = prune_expression(op_expr, base);
      }
   }

   /* If we got here we could not discard any of the operands of the minmax
    * expression, but we can still try to resolve the expression if both
    * operands are constant. We do this after the loop above, to make sure
    * that if our operands are minmax expressions we have tried to prune them
    * first (hopefully reducing them to constants).
    */
   ir_constant *a = expr->operands[0]->as_constant();
   ir_constant *b = expr->operands[1]->as_constant();
   if (a && b)
      return combine_constant(ismin, a, b);

   return expr;
}

static ir_rvalue *
swizzle_if_required(ir_expression *expr, ir_rvalue *rval)
{
   if (expr->type->is_vector() && rval->type->is_scalar()) {
      return swizzle(rval, SWIZZLE_XXXX, expr->type->vector_elements);
   } else {
      return rval;
   }
}

void
ir_minmax_visitor::handle_rvalue(ir_rvalue **rvalue)
{
   if (!*rvalue)
      return;

   ir_expression *expr = (*rvalue)->as_expression();
   if (!expr || (expr->operation != ir_binop_min &&
                 expr->operation != ir_binop_max))
      return;

   ir_rvalue *new_rvalue = prune_expression(expr, minmax_range());
   if (new_rvalue == *rvalue)
      return;

   /* If the expression type is a vector and the optimization leaves a scalar
    * as the result, we need to turn it into a vector.
    */
   *rvalue = swizzle_if_required(expr, new_rvalue);

   progress = true;
}

}

bool
do_minmax_prune(exec_list *instructions)
{
   ir_minmax_visitor v;

   visit_list_elements(&v, instructions);

   return v.progress;
}
