+static void lightrec_modify_lui(struct block *block, unsigned int offset)
+{
+ union code c, *lui = &block->opcode_list[offset].c;
+ bool stop = false, stop_next = false;
+ unsigned int i;
+
+ for (i = offset + 1; !stop && i < block->nb_ops; i++) {
+ c = block->opcode_list[i].c;
+ stop = stop_next;
+
+ if ((opcode_is_store(c) && c.i.rt == lui->i.rt)
+ || (!opcode_is_load(c) && opcode_reads_register(c, lui->i.rt)))
+ break;
+
+ if (opcode_writes_register(c, lui->i.rt)) {
+ if (c.i.op == OP_LWL || c.i.op == OP_LWR) {
+ /* LWL/LWR only partially write their target register;
+ * therefore the LUI should not write a different value. */
+ break;
+ }
+
+ pr_debug("Convert LUI at offset 0x%x to kuseg\n",
+ i - 1 << 2);
+ lui->i.imm = kunseg(lui->i.imm << 16) >> 16;
+ break;
+ }
+
+ if (has_delay_slot(c))
+ stop_next = true;
+ }
+}
+
+static int lightrec_transform_branches(struct lightrec_state *state,
+ struct block *block)
+{
+ struct opcode *op;
+ unsigned int i;
+ s32 offset;
+
+ for (i = 0; i < block->nb_ops; i++) {
+ op = &block->opcode_list[i];
+
+ switch (op->i.op) {
+ case OP_J:
+ /* Transform J opcode into BEQ $zero, $zero if possible. */
+ offset = (s32)((block->pc & 0xf0000000) >> 2 | op->j.imm)
+ - (s32)(block->pc >> 2) - (s32)i - 1;
+
+ if (offset == (s16)offset) {
+ pr_debug("Transform J into BEQ $zero, $zero\n");
+ op->i.op = OP_BEQ;
+ op->i.rs = 0;
+ op->i.rt = 0;
+ op->i.imm = offset;
+
+ }
+ fallthrough;
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static inline bool is_power_of_two(u32 value)
+{
+ return popcount32(value) == 1;
+}
+
+static void lightrec_patch_known_zero(struct opcode *op,
+ const struct constprop_data *v)
+{
+ switch (op->i.op) {
+ case OP_SPECIAL:
+ switch (op->r.op) {
+ case OP_SPECIAL_JR:
+ case OP_SPECIAL_JALR:
+ case OP_SPECIAL_MTHI:
+ case OP_SPECIAL_MTLO:
+ if (is_known_zero(v, op->r.rs))
+ op->r.rs = 0;
+ break;
+ default:
+ if (is_known_zero(v, op->r.rs))
+ op->r.rs = 0;
+ fallthrough;
+ case OP_SPECIAL_SLL:
+ case OP_SPECIAL_SRL:
+ case OP_SPECIAL_SRA:
+ if (is_known_zero(v, op->r.rt))
+ op->r.rt = 0;
+ break;
+ case OP_SPECIAL_SYSCALL:
+ case OP_SPECIAL_BREAK:
+ case OP_SPECIAL_MFHI:
+ case OP_SPECIAL_MFLO:
+ break;
+ }
+ break;
+ case OP_CP0:
+ switch (op->r.rs) {
+ case OP_CP0_MTC0:
+ case OP_CP0_CTC0:
+ if (is_known_zero(v, op->r.rt))
+ op->r.rt = 0;
+ break;
+ default:
+ break;
+ }
+ break;
+ case OP_CP2:
+ if (op->r.op == OP_CP2_BASIC) {
+ switch (op->r.rs) {
+ case OP_CP2_BASIC_MTC2:
+ case OP_CP2_BASIC_CTC2:
+ if (is_known_zero(v, op->r.rt))
+ op->r.rt = 0;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case OP_BEQ:
+ case OP_BNE:
+ if (is_known_zero(v, op->i.rt))
+ op->i.rt = 0;
+ fallthrough;
+ case OP_REGIMM:
+ case OP_BLEZ:
+ case OP_BGTZ:
+ case OP_ADDI:
+ case OP_ADDIU:
+ case OP_SLTI:
+ case OP_SLTIU:
+ case OP_ANDI:
+ case OP_ORI:
+ case OP_XORI:
+ case OP_META_MULT2:
+ case OP_META_MULTU2:
+ case OP_META:
+ if (is_known_zero(v, op->m.rs))
+ op->m.rs = 0;
+ break;
+ case OP_SB:
+ case OP_SH:
+ case OP_SWL:
+ case OP_SW:
+ case OP_SWR:
+ if (is_known_zero(v, op->i.rt))
+ op->i.rt = 0;
+ fallthrough;
+ case OP_LB:
+ case OP_LH:
+ case OP_LWL:
+ case OP_LW:
+ case OP_LBU:
+ case OP_LHU:
+ case OP_LWR:
+ case OP_LWC2:
+ case OP_SWC2:
+ if (is_known(v, op->i.rs)
+ && kunseg(v[op->i.rs].value) == 0)
+ op->i.rs = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+static void lightrec_reset_syncs(struct block *block)
+{
+ struct opcode *op, *list = block->opcode_list;
+ unsigned int i;
+ s32 offset;
+
+ for (i = 0; i < block->nb_ops; i++)
+ list[i].flags &= ~LIGHTREC_SYNC;
+
+ for (i = 0; i < block->nb_ops; i++) {
+ op = &list[i];
+
+ if (has_delay_slot(op->c)) {
+ if (op_flag_local_branch(op->flags)) {
+ offset = i + 1 - op_flag_no_ds(op->flags) + (s16)op->i.imm;
+ list[offset].flags |= LIGHTREC_SYNC;
+ }
+
+ if (op_flag_emulate_branch(op->flags) && i + 2 < block->nb_ops)
+ list[i + 2].flags |= LIGHTREC_SYNC;
+ }
+ }
+}
+
+static int lightrec_transform_ops(struct lightrec_state *state, struct block *block)
+{
+ struct opcode *op, *list = block->opcode_list;
+ struct constprop_data v[32] = LIGHTREC_CONSTPROP_INITIALIZER;
+ unsigned int i;
+ bool local;
+ u8 tmp;
+
+ for (i = 0; i < block->nb_ops; i++) {
+ op = &list[i];
+
+ lightrec_consts_propagate(block, i, v);
+
+ lightrec_patch_known_zero(op, v);
+
+ /* Transform all opcodes detected as useless to real NOPs
+ * (0x0: SLL r0, r0, #0) */
+ if (op->opcode != 0 && is_nop(op->c)) {
+ pr_debug("Converting useless opcode 0x%08x to NOP\n",
+ op->opcode);
+ op->opcode = 0x0;
+ }
+
+ if (!op->opcode)
+ continue;
+
+ switch (op->i.op) {
+ case OP_BEQ:
+ if (op->i.rs == op->i.rt ||
+ (is_known(v, op->i.rs) && is_known(v, op->i.rt) &&
+ v[op->i.rs].value == v[op->i.rt].value)) {
+ if (op->i.rs != op->i.rt)
+ pr_debug("Found always-taken BEQ\n");
+
+ op->i.rs = 0;
+ op->i.rt = 0;
+ } else if (v[op->i.rs].known & v[op->i.rt].known &
+ (v[op->i.rs].value ^ v[op->i.rt].value)) {
+ pr_debug("Found never-taken BEQ\n");
+
+ local = op_flag_local_branch(op->flags);
+ op->opcode = 0;
+ op->flags = 0;
+
+ if (local)
+ lightrec_reset_syncs(block);
+ } else if (op->i.rs == 0) {