/* * Simplify - do instruction simplification before CSE * * Copyright (C) 2004 Linus Torvalds */ #include #include "parse.h" #include "expression.h" #include "linearize.h" #include "flow.h" /* Find the trivial parent for a phi-source */ static struct basic_block *phi_parent(struct basic_block *source, pseudo_t pseudo) { /* Can't go upwards if the pseudo is defined in the bb it came from.. */ if (pseudo->type == PSEUDO_REG) { struct instruction *def = pseudo->def; if (def->bb == source) return source; } if (bb_list_size(source->children) != 1 || bb_list_size(source->parents) != 1) return source; return first_basic_block(source->parents); } static void clear_phi(struct instruction *insn) { pseudo_t phi; insn->bb = NULL; FOR_EACH_PTR(insn->phi_list, phi) { *THIS_ADDRESS(phi) = VOID; } END_FOR_EACH_PTR(phi); } static int if_convert_phi(struct instruction *insn) { pseudo_t array[3]; struct basic_block *parents[3]; struct basic_block *bb, *bb1, *bb2, *source; struct instruction *br; pseudo_t p1, p2; bb = insn->bb; if (linearize_ptr_list((struct ptr_list *)insn->phi_list, (void **)array, 3) != 2) return 0; if (linearize_ptr_list((struct ptr_list *)bb->parents, (void **)parents, 3) != 2) return 0; p1 = array[0]->def->src1; bb1 = array[0]->def->bb; p2 = array[1]->def->src1; bb2 = array[1]->def->bb; /* Only try the simple "direct parents" case */ if ((bb1 != parents[0] || bb2 != parents[1]) && (bb1 != parents[1] || bb2 != parents[0])) return 0; /* * See if we can find a common source for this.. */ source = phi_parent(bb1, p1); if (source != phi_parent(bb2, p2)) return 0; /* * Cool. We now know that 'source' is the exclusive * parent of both phi-nodes, so the exit at the * end of it fully determines which one it is, and * we can turn it into a select. * * HOWEVER, right now we only handle regular * conditional branches. No multijumps or computed * stuff. Verify that here. */ br = last_instruction(source->insns); if (!br || br->opcode != OP_BR) return 0; assert(br->cond); assert(br->bb_false); /* * We're in business. Match up true/false with p1/p2. */ if (br->bb_true == bb2 || br->bb_false == bb1) { pseudo_t p = p1; p1 = p2; p2 = p; } /* * OK, we can now replace that last * * br cond, a, b * * with the sequence * * setcc cond * select pseudo, p1, p2 * br cond, a, b * * and remove the phi-node. If it then * turns out that 'a' or 'b' is entirely * empty (common case), and now no longer * a phi-source, we'll be able to simplify * the conditional branch too. */ insert_select(source, br, insn, p1, p2); clear_phi(insn); return REPEAT_CSE; } static int clean_up_phi(struct instruction *insn) { pseudo_t phi; struct instruction *last; int same; last = NULL; same = 1; FOR_EACH_PTR(insn->phi_list, phi) { struct instruction *def; if (phi == VOID) continue; def = phi->def; if (def->src1 == VOID || !def->bb) continue; if (last) { if (last->src1 != def->src1) same = 0; continue; } last = def; } END_FOR_EACH_PTR(phi); if (same) { pseudo_t pseudo = last ? last->src1 : VOID; convert_instruction_target(insn, pseudo); clear_phi(insn); return REPEAT_CSE; } return if_convert_phi(insn); } static int delete_pseudo_user_list_entry(struct pseudo_user_list **list, pseudo_t *entry, int count) { struct pseudo_user *pu; FOR_EACH_PTR(*list, pu) { if (pu->userp == entry) { DELETE_CURRENT_PTR(pu); if (!--count) goto out; } } END_FOR_EACH_PTR(pu); assert(count <= 0); out: pack_ptr_list((struct ptr_list **)list); return count; } static inline void remove_usage(pseudo_t p, pseudo_t *usep) { if (has_use_list(p)) { delete_pseudo_user_list_entry(&p->users, usep, 1); if (!p->users) kill_instruction(p->def); } } void kill_use(pseudo_t *usep) { if (usep) { pseudo_t p = *usep; *usep = VOID; remove_usage(p, usep); } } void kill_instruction(struct instruction *insn) { if (!insn || !insn->bb) return; switch (insn->opcode) { case OP_BINARY ... OP_BINCMP_END: insn->bb = NULL; kill_use(&insn->src1); kill_use(&insn->src2); repeat_phase |= REPEAT_CSE; return; case OP_NOT: case OP_NEG: insn->bb = NULL; kill_use(&insn->src1); repeat_phase |= REPEAT_CSE; return; case OP_PHI: insn->bb = NULL; repeat_phase |= REPEAT_CSE; return; case OP_SYMADDR: insn->bb = NULL; repeat_phase |= REPEAT_CSE | REPEAT_SYMBOL_CLEANUP; return; case OP_RANGE: insn->bb = NULL; repeat_phase |= REPEAT_CSE; kill_use(&insn->src1); kill_use(&insn->src2); kill_use(&insn->src3); return; case OP_BR: insn->bb = NULL; repeat_phase |= REPEAT_CSE; if (insn->cond) kill_use(&insn->cond); return; } } /* * Kill trivially dead instructions */ static int dead_insn(struct instruction *insn, pseudo_t *src1, pseudo_t *src2, pseudo_t *src3) { struct pseudo_user *pu; FOR_EACH_PTR(insn->target->users, pu) { if (*pu->userp != VOID) return 0; } END_FOR_EACH_PTR(pu); insn->bb = NULL; kill_use(src1); kill_use(src2); kill_use(src3); return REPEAT_CSE; } static inline int constant(pseudo_t pseudo) { return pseudo->type == PSEUDO_VAL; } static int replace_with_pseudo(struct instruction *insn, pseudo_t pseudo) { convert_instruction_target(insn, pseudo); insn->bb = NULL; return REPEAT_CSE; } static unsigned int value_size(long long value) { value >>= 8; if (!value) return 8; value >>= 8; if (!value) return 16; value >>= 16; if (!value) return 32; return 64; } /* * Try to determine the maximum size of bits in a pseudo. * * Right now this only follow casts and constant values, but we * could look at things like logical 'and' instructions etc. */ static unsigned int operand_size(struct instruction *insn, pseudo_t pseudo) { unsigned int size = insn->size; if (pseudo->type == PSEUDO_REG) { struct instruction *src = pseudo->def; if (src && src->opcode == OP_CAST && src->orig_type) { unsigned int orig_size = src->orig_type->bit_size; if (orig_size < size) size = orig_size; } } if (pseudo->type == PSEUDO_VAL) { unsigned int orig_size = value_size(pseudo->value); if (orig_size < size) size = orig_size; } return size; } static int simplify_asr(struct instruction *insn, pseudo_t pseudo, long long value) { unsigned int size = operand_size(insn, pseudo); if (value >= size) { warning(insn->pos, "right shift by bigger than source value"); return replace_with_pseudo(insn, value_pseudo(0)); } if (!value) return replace_with_pseudo(insn, pseudo); return 0; } static int simplify_constant_rightside(struct instruction *insn) { long long value = insn->src2->value; switch (insn->opcode) { case OP_SUB: if (value) { insn->opcode = OP_ADD; insn->src2 = value_pseudo(-value); return REPEAT_CSE; } /* Fall through */ case OP_ADD: case OP_OR: case OP_XOR: case OP_OR_BOOL: case OP_SHL: case OP_LSR: if (!value) return replace_with_pseudo(insn, insn->src1); return 0; case OP_ASR: return simplify_asr(insn, insn->src1, value); case OP_MULU: case OP_MULS: case OP_AND_BOOL: if (value == 1) return replace_with_pseudo(insn, insn->src1); /* Fall through */ case OP_AND: if (!value) return replace_with_pseudo(insn, insn->src2); return 0; } return 0; } static int simplify_constant_leftside(struct instruction *insn) { long long value = insn->src1->value; switch (insn->opcode) { case OP_ADD: case OP_OR: case OP_XOR: if (!value) return replace_with_pseudo(insn, insn->src2); return 0; case OP_SHL: case OP_LSR: case OP_ASR: case OP_AND: case OP_MULU: case OP_MULS: if (!value) return replace_with_pseudo(insn, insn->src1); return 0; } return 0; } static int simplify_constant_binop(struct instruction *insn) { /* FIXME! Verify signs and sizes!! */ long long left = insn->src1->value; long long right = insn->src2->value; unsigned long long ul, ur; long long res, mask, bits; mask = 1ULL << (insn->size-1); bits = mask | (mask-1); if (left & mask) left |= ~bits; if (right & mask) right |= ~bits; ul = left & bits; ur = right & bits; switch (insn->opcode) { case OP_ADD: res = left + right; break; case OP_SUB: res = left - right; break; case OP_MULU: res = ul * ur; break; case OP_MULS: res = left * right; break; case OP_DIVU: if (!ur) return 0; res = ul / ur; break; case OP_DIVS: if (!right) return 0; res = left / right; break; case OP_MODU: if (!ur) return 0; res = ul % ur; break; case OP_MODS: if (!right) return 0; res = left % right; break; case OP_SHL: res = left << right; break; case OP_LSR: res = ul >> ur; break; case OP_ASR: res = left >> right; break; /* Logical */ case OP_AND: res = left & right; break; case OP_OR: res = left | right; break; case OP_XOR: res = left ^ right; break; case OP_AND_BOOL: res = left && right; break; case OP_OR_BOOL: res = left || right; break; /* Binary comparison */ case OP_SET_EQ: res = left == right; break; case OP_SET_NE: res = left != right; break; case OP_SET_LE: res = left <= right; break; case OP_SET_GE: res = left >= right; break; case OP_SET_LT: res = left < right; break; case OP_SET_GT: res = left > right; break; case OP_SET_B: res = ul < ur; break; case OP_SET_A: res = ul > ur; break; case OP_SET_BE: res = ul <= ur; break; case OP_SET_AE: res = ul >= ur; break; default: return 0; } res &= bits; replace_with_pseudo(insn, value_pseudo(res)); return REPEAT_CSE; } static int simplify_binop(struct instruction *insn) { if (dead_insn(insn, &insn->src1, &insn->src2, NULL)) return REPEAT_CSE; if (constant(insn->src1)) { if (constant(insn->src2)) return simplify_constant_binop(insn); return simplify_constant_leftside(insn); } if (constant(insn->src2)) return simplify_constant_rightside(insn); return 0; } static void switch_pseudo(struct instruction *insn1, pseudo_t *pp1, struct instruction *insn2, pseudo_t *pp2) { pseudo_t p1 = *pp1, p2 = *pp2; use_pseudo(insn1, p2, pp1); use_pseudo(insn2, p1, pp2); remove_usage(p1, pp1); remove_usage(p2, pp2); } static int canonical_order(pseudo_t p1, pseudo_t p2) { /* symbol/constants on the right */ if (p1->type == PSEUDO_VAL) return p2->type == PSEUDO_VAL; if (p1->type == PSEUDO_SYM) return p2->type == PSEUDO_SYM || p2->type == PSEUDO_VAL; return 1; } static int simplify_commutative_binop(struct instruction *insn) { if (!canonical_order(insn->src1, insn->src2)) { switch_pseudo(insn, &insn->src1, insn, &insn->src2); return REPEAT_CSE; } return 0; } static inline int simple_pseudo(pseudo_t pseudo) { return pseudo->type == PSEUDO_VAL || pseudo->type == PSEUDO_SYM; } static int simplify_associative_binop(struct instruction *insn) { struct instruction *def; pseudo_t pseudo = insn->src1; if (!simple_pseudo(insn->src2)) return 0; if (pseudo->type != PSEUDO_REG) return 0; def = pseudo->def; if (def == insn) return 0; if (def->opcode != insn->opcode) return 0; if (!simple_pseudo(def->src2)) return 0; if (ptr_list_size((struct ptr_list *)def->target->users) != 1) return 0; switch_pseudo(def, &def->src1, insn, &insn->src2); return REPEAT_CSE; } static int simplify_constant_unop(struct instruction *insn) { long long val = insn->src1->value; long long res, mask; switch (insn->opcode) { case OP_NOT: res = ~val; break; case OP_NEG: res = -val; break; default: return 0; } mask = 1ULL << (insn->size-1); res &= mask | (mask-1); replace_with_pseudo(insn, value_pseudo(res)); return REPEAT_CSE; } static int simplify_unop(struct instruction *insn) { if (dead_insn(insn, &insn->src1, NULL, NULL)) return REPEAT_CSE; if (constant(insn->src1)) return simplify_constant_unop(insn); return 0; } static int simplify_one_memop(struct instruction *insn, pseudo_t orig) { pseudo_t addr = insn->src; pseudo_t new, off; if (addr->type == PSEUDO_REG) { struct instruction *def = addr->def; if (def->opcode == OP_SYMADDR && def->src) { kill_use(&insn->src); use_pseudo(insn, def->src, &insn->src); return REPEAT_CSE | REPEAT_SYMBOL_CLEANUP; } if (def->opcode == OP_ADD) { new = def->src1; off = def->src2; if (constant(off)) goto offset; new = off; off = def->src1; if (constant(off)) goto offset; return 0; } } return 0; offset: /* Invalid code */ if (new == orig) { if (new == VOID) return 0; new = VOID; warning(insn->pos, "crazy programmer"); } insn->offset += off->value; use_pseudo(insn, new, &insn->src); remove_usage(addr, &insn->src); return REPEAT_CSE | REPEAT_SYMBOL_CLEANUP; } /* * We walk the whole chain of adds/subs backwards. That's not * only more efficient, but it allows us to find loops. */ static int simplify_memop(struct instruction *insn) { int one, ret = 0; pseudo_t orig = insn->src; do { one = simplify_one_memop(insn, orig); ret |= one; } while (one); return ret; } static long long get_cast_value(long long val, int old_size, int new_size, int sign) { long long mask; if (sign && new_size > old_size) { mask = 1 << (old_size-1); if (val & mask) val |= ~(mask | (mask-1)); } mask = 1 << (new_size-1); return val & (mask | (mask-1)); } static int simplify_cast(struct instruction *insn) { struct symbol *orig_type; int orig_size, size; pseudo_t src; if (dead_insn(insn, &insn->src, NULL, NULL)) return REPEAT_CSE; orig_type = insn->orig_type; if (!orig_type) return 0; orig_size = orig_type->bit_size; size = insn->size; src = insn->src; /* A cast of a constant? */ if (constant(src)) { int sign = orig_type->ctype.modifiers & MOD_SIGNED; long long val = get_cast_value(src->value, orig_size, size, sign); src = value_pseudo(val); goto simplify; } /* A cast of a "and" might be a no-op.. */ if (src->type == PSEUDO_REG) { struct instruction *def = src->def; if (def->opcode == OP_AND && def->size >= size) { pseudo_t val = def->src2; if (val->type == PSEUDO_VAL) { unsigned long long value = val->value; if (!(value >> (size-1))) goto simplify; } } } if (size == orig_size) { int op = (orig_type->ctype.modifiers & MOD_SIGNED) ? OP_SCAST : OP_CAST; if (insn->opcode == op) goto simplify; } return 0; simplify: return replace_with_pseudo(insn, src); } static int simplify_select(struct instruction *insn) { pseudo_t cond, src1, src2; if (dead_insn(insn, &insn->src1, &insn->src2, &insn->src3)) return REPEAT_CSE; cond = insn->src1; src1 = insn->src2; src2 = insn->src3; if (constant(cond) || src1 == src2) { pseudo_t *kill, take; kill_use(&insn->src1); take = cond->value ? src1 : src2; kill = cond->value ? &insn->src3 : &insn->src2; kill_use(kill); replace_with_pseudo(insn, take); return REPEAT_CSE; } if (constant(src1) && constant(src2)) { long long val1 = src1->value; long long val2 = src2->value; /* The pair 0/1 is special - replace with SETNE/SETEQ */ if ((val1 | val2) == 1) { int opcode = OP_SET_EQ; if (val1) { src1 = src2; opcode = OP_SET_NE; } insn->opcode = opcode; /* insn->src1 is already cond */ insn->src2 = src1; /* Zero */ return REPEAT_CSE; } } return 0; } static int is_in_range(pseudo_t src, long long low, long long high) { long long value; switch (src->type) { case PSEUDO_VAL: value = src->value; return value >= low && value <= high; default: return 0; } } static int simplify_range(struct instruction *insn) { pseudo_t src1, src2, src3; src1 = insn->src1; src2 = insn->src2; src3 = insn->src3; if (src2->type != PSEUDO_VAL || src3->type != PSEUDO_VAL) return 0; if (is_in_range(src1, src2->value, src3->value)) { kill_instruction(insn); return REPEAT_CSE; } return 0; } /* * Simplify "set_ne/eq $0 + br" */ static int simplify_cond_branch(struct instruction *br, pseudo_t cond, struct instruction *def, pseudo_t *pp) { use_pseudo(br, *pp, &br->cond); remove_usage(cond, &br->cond); if (def->opcode == OP_SET_EQ) { struct basic_block *true = br->bb_true; struct basic_block *false = br->bb_false; br->bb_false = true; br->bb_true = false; } return REPEAT_CSE; } static int simplify_branch(struct instruction *insn) { pseudo_t cond = insn->cond; if (!cond) return 0; /* Constant conditional */ if (constant(cond)) { insert_branch(insn->bb, insn, cond->value ? insn->bb_true : insn->bb_false); return REPEAT_CSE; } /* Same target? */ if (insn->bb_true == insn->bb_false) { struct basic_block *bb = insn->bb; struct basic_block *target = insn->bb_false; remove_bb_from_list(&target->parents, bb, 1); remove_bb_from_list(&bb->children, target, 1); insn->bb_false = NULL; kill_use(&insn->cond); insn->cond = NULL; return REPEAT_CSE; } /* Conditional on a SETNE $0 or SETEQ $0 */ if (cond->type == PSEUDO_REG) { struct instruction *def = cond->def; if (def->opcode == OP_SET_NE || def->opcode == OP_SET_EQ) { if (constant(def->src1) && !def->src1->value) return simplify_cond_branch(insn, cond, def, &def->src2); if (constant(def->src2) && !def->src2->value) return simplify_cond_branch(insn, cond, def, &def->src1); } if (def->opcode == OP_SEL) { if (constant(def->src2) && constant(def->src3)) { long long val1 = def->src2->value; long long val2 = def->src3->value; if (!val1 && !val2) { insert_branch(insn->bb, insn, insn->bb_false); return REPEAT_CSE; } if (val1 && val2) { insert_branch(insn->bb, insn, insn->bb_true); return REPEAT_CSE; } if (val2) { struct basic_block *true = insn->bb_true; struct basic_block *false = insn->bb_false; insn->bb_false = true; insn->bb_true = false; } use_pseudo(insn, def->src1, &insn->cond); remove_usage(cond, &insn->cond); return REPEAT_CSE; } } if (def->opcode == OP_CAST || def->opcode == OP_SCAST) { int orig_size = def->orig_type ? def->orig_type->bit_size : 0; if (def->size > orig_size) { use_pseudo(insn, def->src, &insn->cond); remove_usage(cond, &insn->cond); return REPEAT_CSE; } } } return 0; } static int simplify_switch(struct instruction *insn) { pseudo_t cond = insn->cond; long long val; struct multijmp *jmp; if (!constant(cond)) return 0; val = insn->cond->value; FOR_EACH_PTR(insn->multijmp_list, jmp) { /* Default case */ if (jmp->begin > jmp->end) goto found; if (val >= jmp->begin && val <= jmp->end) goto found; } END_FOR_EACH_PTR(jmp); warning(insn->pos, "Impossible case statement"); return 0; found: insert_branch(insn->bb, insn, jmp->target); return REPEAT_CSE; } int simplify_instruction(struct instruction *insn) { if (!insn->bb) return 0; switch (insn->opcode) { case OP_ADD: case OP_MULS: case OP_AND: case OP_OR: case OP_XOR: case OP_AND_BOOL: case OP_OR_BOOL: if (simplify_binop(insn)) return REPEAT_CSE; if (simplify_commutative_binop(insn)) return REPEAT_CSE; return simplify_associative_binop(insn); case OP_MULU: case OP_SET_EQ: case OP_SET_NE: if (simplify_binop(insn)) return REPEAT_CSE; return simplify_commutative_binop(insn); case OP_SUB: case OP_DIVU: case OP_DIVS: case OP_MODU: case OP_MODS: case OP_SHL: case OP_LSR: case OP_ASR: case OP_SET_LE: case OP_SET_GE: case OP_SET_LT: case OP_SET_GT: case OP_SET_B: case OP_SET_A: case OP_SET_BE: case OP_SET_AE: return simplify_binop(insn); case OP_NOT: case OP_NEG: return simplify_unop(insn); case OP_LOAD: case OP_STORE: return simplify_memop(insn); case OP_SYMADDR: if (dead_insn(insn, NULL, NULL, NULL)) return REPEAT_CSE | REPEAT_SYMBOL_CLEANUP; return replace_with_pseudo(insn, insn->symbol); case OP_CAST: case OP_SCAST: case OP_FPCAST: case OP_PTRCAST: return simplify_cast(insn); case OP_PHI: if (dead_insn(insn, NULL, NULL, NULL)) { clear_phi(insn); return REPEAT_CSE; } return clean_up_phi(insn); case OP_PHISOURCE: if (dead_insn(insn, &insn->phi_src, NULL, NULL)) return REPEAT_CSE; break; case OP_SEL: return simplify_select(insn); case OP_BR: return simplify_branch(insn); case OP_SWITCH: return simplify_switch(insn); case OP_RANGE: return simplify_range(insn); } return 0; }