text: /**
   * \file
   * Constant folding support
   *
   * Author:
   *   Paolo Molaro (lupus@ximian.com)
   *   Dietmar Maurer (dietmar@ximian.com)
   *
   * (C) 2003 Ximian, Inc.  http://www.ximian.com
   */
  #include <config.h>
  
  #include "mini.h"
  #include "ir-emit.h"
  
  /* WTF is this doing here?!?!? */
  int
  mono_is_power_of_two (guint32 val)
  {
  	int i, j, k;
  
  	for (i = 0, j = 1, k = 0xfffffffe; i < 32; ++i, j = j << 1, k = k << 1) {
  		if (val & j)
  			break;
  	}
  	if (i == 32 || val & k)
  		return -1;
  	return i;
  }
  
  #ifndef G_MININT32
  #define MYGINT32_MAX 2147483647
  #define G_MININT32 (-MYGINT32_MAX -1)
  #endif
  
  #define FOLD_UNOP(name,op)	\
  	case name:	\
  	    dest->inst_c0 = op arg1->inst_c0; \
          break;
  
  #define FOLD_BINOP(name, op) \
  	case name:	\
  	    dest->inst_c0 = arg1->inst_c0 op arg2->inst_c0;	\
          break;
  
  #define FOLD_BINOPC(name,op,cast)	\
  	case name:	\
  	    dest->inst_c0 = (cast)arg1->inst_c0 op (cast)arg2->inst_c0;	\
          break;
  
  #define FOLD_BINOP2_IMM(name, op) \
  	case name:	\
  	    dest->inst_c0 = arg1->inst_c0 op ins->inst_imm;	\
          break;
  
  #define FOLD_BINOPC2_IMM(name, op, cast) \
  	case name:	\
  	    dest->inst_c0 = (cast)arg1->inst_c0 op (cast)ins->inst_imm;	\
          break;
  
  #define FOLD_BINOPCXX(name,op,cast)	\
  	case name:	\
  	    res = (cast)arg1->inst_c0 op (cast)arg2->inst_c0;	\
          break; \
  
  #define ALLOC_DEST(cfg, dest, ins) do { \
      if (!(dest)) { \
          MONO_INST_NEW ((cfg), (dest), -1); \
          (dest)->dreg = (ins)->dreg; \
      } \
  } while (0)
  
  #ifndef DISABLE_JIT
  
  /**
   * mono_constant_fold_ins:
   *
   * Perform constant folding on INS, using ARG1 and ARG2 as the arguments. If OVERWRITE is
   * true, then store the result back into INS and return INS. Otherwise allocate a new ins,
   * store the result into it and return it. If constant folding cannot be performed, return
   * NULL.
   */
  MonoInst*
  mono_constant_fold_ins (MonoCompile *cfg, MonoInst *ins, MonoInst *arg1, MonoInst *arg2, gboolean overwrite)
  {
  	MonoInst *dest = NULL;
  
  	if (overwrite)
  		dest = ins;
  
  	switch (ins->opcode) {
  	case OP_IMUL:
  	case OP_IADD:
  	case OP_IAND:
  	case OP_IOR:
  	case OP_IXOR:
  		if (arg2->opcode == OP_ICONST) {
  			if (arg1->opcode == OP_ICONST) {
  				ALLOC_DEST (cfg, dest, ins);
  				switch (ins->opcode) {
  					FOLD_BINOP (OP_IMUL, *);
  					FOLD_BINOP (OP_IADD, +);
  					FOLD_BINOP (OP_IAND, &);
  					FOLD_BINOP (OP_IOR, |);
  					FOLD_BINOP (OP_IXOR, ^);
  				}
  				dest->opcode = OP_ICONST;
  				MONO_INST_NULLIFY_SREGS (dest);
  			}
  		} else if (arg1->opcode == OP_ICONST) {
  			/* 
  			 * This is commutative so swap the arguments, allowing the _imm variant
  			 * to be used later.
  			 */
  			if (mono_op_to_op_imm (ins->opcode) != -1) {
  				ALLOC_DEST (cfg, dest, ins);
  				dest->opcode = mono_op_to_op_imm (ins->opcode);
  				dest->sreg1 = ins->sreg2;
  				dest->sreg2 = -1;
  				dest->inst_imm = arg1->inst_c0;
  			}
  		}
  		break;
  	case OP_IMUL_IMM:
  	case OP_IADD_IMM:
  	case OP_IAND_IMM:
  	case OP_IOR_IMM:
  	case OP_IXOR_IMM:
  	case OP_ISUB_IMM:
  	case OP_ISHL_IMM:
  	case OP_ISHR_IMM:
  	case OP_ISHR_UN_IMM:
  	case OP_SHL_IMM:
  		if (arg1->opcode == OP_ICONST) {
  			ALLOC_DEST (cfg, dest, ins);
  			switch (ins->opcode) {
  				FOLD_BINOP2_IMM (OP_IMUL_IMM, *);
  				FOLD_BINOP2_IMM (OP_IADD_IMM, +);
  				FOLD_BINOP2_IMM (OP_IAND_IMM, &);
  				FOLD_BINOP2_IMM (OP_IOR_IMM, |);
  				FOLD_BINOP2_IMM (OP_IXOR_IMM, ^);
  				FOLD_BINOP2_IMM (OP_ISUB_IMM, -);
  				FOLD_BINOPC2_IMM (OP_ISHL_IMM, <<, gint32);
  				FOLD_BINOPC2_IMM (OP_ISHR_IMM, >>, gint32);
  				FOLD_BINOPC2_IMM (OP_ISHR_UN_IMM, >>, guint32);
  				FOLD_BINOP2_IMM (OP_SHL_IMM, <<);
  			}
  			dest->opcode = OP_ICONST;
  			MONO_INST_NULLIFY_SREGS (dest);
  		}
  		break;
  	case OP_ISUB:
  	case OP_ISHL:
  	case OP_ISHR:
  	case OP_ISHR_UN:
  		if ((arg1->opcode == OP_ICONST) && (arg2->opcode == OP_ICONST)) {
  			ALLOC_DEST (cfg, dest, ins);
  			switch (ins->opcode) {
  				FOLD_BINOP (OP_ISUB, -);
  				FOLD_BINOP (OP_ISHL, <<);
  				FOLD_BINOP (OP_ISHR, >>);
  				FOLD_BINOPC (OP_ISHR_UN, >>, guint32);
  			}
  			dest->opcode = OP_ICONST;
  			MONO_INST_NULLIFY_SREGS (dest);
  		}
  		break;
  	case OP_IDIV:
  	case OP_IDIV_UN:
  	case OP_IREM:
  	case OP_IREM_UN:
  		if ((arg1->opcode == OP_ICONST) && (arg2->opcode == OP_ICONST)) {
  			if ((arg2->inst_c0 == 0) || ((arg1->inst_c0 == G_MININT32) && (arg2->inst_c0 == -1)))
  				return NULL;
  			ALLOC_DEST (cfg, dest, ins);
  			switch (ins->opcode) {
  				FOLD_BINOPC (OP_IDIV, /, gint32);
  				FOLD_BINOPC (OP_IDIV_UN, /, guint32);
  				FOLD_BINOPC (OP_IREM, %, gint32);
  				FOLD_BINOPC (OP_IREM_UN, %, guint32);
  			}
  			dest->opcode = OP_ICONST;
  			MONO_INST_NULLIFY_SREGS (dest);
  		}
  		break;
  	case OP_IDIV_IMM:
  	case OP_IDIV_UN_IMM:
  	case OP_IREM_IMM:
  	case OP_IREM_UN_IMM:
  		if (arg1->opcode == OP_ICONST) {
  			if ((ins->inst_imm == 0) || ((arg1->inst_c0 == G_MININT32) && (ins->inst_imm == -1)))
  				return NULL;
  			ALLOC_DEST (cfg, dest, ins);
  			switch (ins->opcode) {
  				FOLD_BINOPC2_IMM (OP_IDIV_IMM, /, gint32);
  				FOLD_BINOPC2_IMM (OP_IDIV_UN_IMM, /, guint32);
  				FOLD_BINOPC2_IMM (OP_IREM_IMM, %, gint32);
  				FOLD_BINOPC2_IMM (OP_IREM_UN_IMM, %, guint32);
  			default:
  				g_assert_not_reached ();
  			}
  			dest->opcode = OP_ICONST;
  			MONO_INST_NULLIFY_SREGS (dest);
  		}
  		break;
  		/* case OP_INEG: */
  	case OP_INOT:
  	case OP_INEG:
  		if (arg1->opcode == OP_ICONST) {
  			/* INEG sets cflags on x86, and the LNEG decomposition depends on that */
  #if SIZEOF_REGISTER == 4
  			if (ins->opcode == OP_INEG)
  				return NULL;
  #endif
  			ALLOC_DEST (cfg, dest, ins);
  			switch (ins->opcode) {
  				FOLD_UNOP (OP_INEG,-);
  				FOLD_UNOP (OP_INOT,~);
  			}
  			dest->opcode = OP_ICONST;
  			MONO_INST_NULLIFY_SREGS (dest);
  		}
  		break;
  	case OP_SEXT_I4:
  		if (arg1->opcode == OP_ICONST && arg1->inst_c0 >= 0 && arg1->inst_c0 < (1 << 16) && overwrite) {
  			dest->opcode = OP_ICONST;
  			dest->sreg1 = -1;
  			dest->inst_c0 = arg1->inst_c0;
  		}
  		break;
  	case OP_MOVE:
  #if SIZEOF_REGISTER == 8
  		if ((arg1->opcode == OP_ICONST) || (arg1->opcode == OP_I8CONST)) {
  #else
  		if (arg1->opcode == OP_ICONST) {
  #endif
  			ALLOC_DEST (cfg, dest, ins);
  			dest->opcode = arg1->opcode;
  			MONO_INST_NULLIFY_SREGS (dest);
  			dest->inst_c0 = arg1->inst_c0;
  		}
  		break;
  	case OP_VMOVE:
  		if (arg1->opcode == OP_VZERO) {
  			ALLOC_DEST (cfg, dest, ins);
  			dest->opcode = OP_VZERO;
  			dest->sreg1 = -1;
  		}
  		break;
  	case OP_XMOVE:
  		if (arg1->opcode == OP_XZERO) {
  			ALLOC_DEST (cfg, dest, ins);
  			dest->opcode = OP_XZERO;
  			dest->sreg1 = -1;
  		}
  		break;
  	case OP_COMPARE:
  	case OP_ICOMPARE:
  	case OP_COMPARE_IMM:
  	case OP_ICOMPARE_IMM: {
  		MonoInst dummy_arg2;
  		if (ins->sreg2 == -1) {
  			arg2 = &dummy_arg2;
  			arg2->opcode = OP_ICONST;
  			arg2->inst_c0 = ins->inst_imm;
  		}
  
  		if ((arg1->opcode == OP_ICONST) && (arg2->opcode == OP_ICONST) && ins->next) {
  			MonoInst *next = ins->next;
  			gboolean res = FALSE;
  
  			switch (next->opcode) {
  			case OP_CEQ:
  			case OP_ICEQ:
  			case OP_CGT:
  			case OP_ICGT:
  			case OP_CGT_UN:
  			case OP_ICGT_UN:
  			case OP_CLT:
  			case OP_ICLT:
  			case OP_CLT_UN:
  			case OP_ICLT_UN:
  				switch (next->opcode) {
  					FOLD_BINOPCXX (OP_CEQ,==,gint32);
  					FOLD_BINOPCXX (OP_ICEQ,==,gint32);
  					FOLD_BINOPCXX (OP_CGT,>,gint32);
  					FOLD_BINOPCXX (OP_ICGT,>,gint32);
  					FOLD_BINOPCXX (OP_CGT_UN,>,guint32);
  					FOLD_BINOPCXX (OP_ICGT_UN,>,guint32);
  					FOLD_BINOPCXX (OP_CLT,<,gint32);
  					FOLD_BINOPCXX (OP_ICLT,<,gint32);
  					FOLD_BINOPCXX (OP_CLT_UN,<,guint32);
  					FOLD_BINOPCXX (OP_ICLT_UN,<,guint32);
  				}
  
  				if (overwrite) {
  					NULLIFY_INS (ins);
  					next->opcode = OP_ICONST;
  					next->inst_c0 = res;
  					MONO_INST_NULLIFY_SREGS (next);
  				} else {
  					ALLOC_DEST (cfg, dest, ins);
  					dest->opcode = OP_ICONST;
  					dest->inst_c0 = res;
  				}
  				break;
  			case OP_IBEQ:
  			case OP_IBNE_UN:
  			case OP_IBGT:
  			case OP_IBGT_UN:
  			case OP_IBGE:
  			case OP_IBGE_UN:
  			case OP_IBLT:
  			case OP_IBLT_UN:
  			case OP_IBLE:
  			case OP_IBLE_UN:
  				switch (next->opcode) {
  					FOLD_BINOPCXX (OP_IBEQ,==,gint32);
  					FOLD_BINOPCXX (OP_IBNE_UN,!=,guint32);
  					FOLD_BINOPCXX (OP_IBGT,>,gint32);
  					FOLD_BINOPCXX (OP_IBGT_UN,>,guint32);
  					FOLD_BINOPCXX (OP_IBGE,>=,gint32);
  					FOLD_BINOPCXX (OP_IBGE_UN,>=,guint32);
  					FOLD_BINOPCXX (OP_IBLT,<,gint32);
  					FOLD_BINOPCXX (OP_IBLT_UN,<,guint32);
  					FOLD_BINOPCXX (OP_IBLE,<=,gint32);
  					FOLD_BINOPCXX (OP_IBLE_UN,<=,guint32);
  				}
  
  				if (overwrite) {
  					/* 
  					 * Can't nullify OP_COMPARE here since the decompose long branch 
  					 * opcodes depend on it being executed. Also, the branch might not
  					 * be eliminated after all if loop opts is disabled, for example.
  					 */
  					if (res)
  						next->flags |= MONO_INST_CFOLD_TAKEN;
  					else
  						next->flags |= MONO_INST_CFOLD_NOT_TAKEN;
  				} else {
  					ALLOC_DEST (cfg, dest, ins);
  					dest->opcode = OP_ICONST;
  					dest->inst_c0 = res;
  				}
  				break;
  			case OP_COND_EXC_EQ:
  				res = arg1->inst_c0 == arg2->inst_c0;
  				if (!res) {
  					if (overwrite) {
  						NULLIFY_INS (ins);
  						NULLIFY_INS (next);
  					} else {
  						ALLOC_DEST (cfg, dest, ins);
  						dest->opcode = OP_ICONST;
  						dest->inst_c0 = res;
  					}
  				}
  				break;
  			case OP_NOP:
  			case OP_BR:
  				/* This happens when a conditional branch is eliminated */
  				if (next->next == NULL) {
  					/* Last ins */
  					if (overwrite)
  						NULLIFY_INS (ins);
  				}
  				break;
  			default:
  				return NULL;
  			}
  		}
  		if ((arg1->opcode == OP_PCONST) && (arg2->opcode == OP_PCONST) && ins->next) {
  			MonoInst *next = ins->next;
  
  			if (next->opcode == OP_LCEQ) {
  				gboolean res = arg1->inst_p0 == arg2->inst_p0;
  				if (overwrite) {
  					NULLIFY_INS (ins);
  					next->opcode = OP_ICONST;
  					next->inst_c0 = res;
  					MONO_INST_NULLIFY_SREGS (next);
  				} else {
  					ALLOC_DEST (cfg, dest, ins);
  					dest->opcode = OP_ICONST;
  					dest->inst_c0 = res;
  				}
  				break;
  			}
  		}
  		break;
  	}
  	case OP_FMOVE:
  		if (arg1->opcode == OP_R8CONST) {
  			ALLOC_DEST (cfg, dest, ins);
  			dest->opcode = OP_R8CONST;
  			dest->sreg1 = -1;
  			dest->inst_p0 = arg1->inst_p0;
  		}
  		break;
  
  		/*
  		 * TODO: 
  		 * 	conv.* opcodes.
  		 * 	*ovf* opcodes? It's slow and hard to do in C.
  		 *      switch can be replaced by a simple jump 
  		 */
  	default:
  		return NULL;
  	}
  		
      return dest;
  }	
  
  
  #endif /* DISABLE_JIT */
  