/** * \file * Constant folding support * * Author: * Paolo Molaro (lupus@ximian.com) * Dietmar Maurer (dietmar@ximian.com) * * (C) 2003 Ximian, Inc. http://www.ximian.com */ #include #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_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 */