+ /* sll rY, rX, 16
+ * ...
+ * srl rY, rY, 16 */
+ to_change = prev;
+ to_nop = curr;
+ }
+
+ idx2 = find_prev_writer(list, idx, prev->r.rt);
+ if (idx2 >= 0) {
+ /* Note that PSX games sometimes do casts after
+ * a LHU or LBU; in this case we can change the
+ * load opcode to a LH or LB, and the cast can
+ * be changed to a MOV or a simple NOP. */
+
+ prev2 = &list[idx2];
+
+ if (curr->r.rd != prev2->i.rt &&
+ !reg_is_dead(list, offset, prev2->i.rt))
+ prev2 = NULL;
+ else if (curr->r.imm == 16 && prev2->i.op == OP_LHU)
+ prev2->i.op = OP_LH;
+ else if (curr->r.imm == 24 && prev2->i.op == OP_LBU)
+ prev2->i.op = OP_LB;
+ else
+ prev2 = NULL;
+
+ if (prev2) {
+ if (curr->r.rd == prev2->i.rt) {
+ to_change->opcode = 0;
+ } else if (reg_is_dead(list, offset, prev2->i.rt) &&
+ !reg_is_read_or_written(list, idx2 + 1, offset, curr->r.rd)) {
+ /* The target register of the SRA is dead after the
+ * LBU/LHU; we can change the target register of the
+ * LBU/LHU to the one of the SRA. */
+ prev2->i.rt = curr->r.rd;
+ to_change->opcode = 0;
+ } else {
+ to_change->i.op = OP_META_MOV;
+ to_change->r.rd = curr->r.rd;
+ to_change->r.rs = prev2->i.rt;
+ }
+
+ if (to_nop->r.imm == 24)
+ pr_debug("Convert LBU+SLL+SRA to LB\n");
+ else
+ pr_debug("Convert LHU+SLL+SRA to LH\n");
+ }
+ }
+
+ if (!prev2) {
+ pr_debug("Convert SLL/SRA #%u to EXT%c\n",
+ prev->r.imm,
+ prev->r.imm == 24 ? 'C' : 'S');
+
+ if (to_change == prev) {
+ to_change->i.rs = prev->r.rt;
+ to_change->i.rt = curr->r.rd;
+ } else {
+ to_change->i.rt = curr->r.rd;
+ to_change->i.rs = prev->r.rt;
+ }
+
+ if (to_nop->r.imm == 24)
+ to_change->i.op = OP_META_EXTC;
+ else
+ to_change->i.op = OP_META_EXTS;
+ }
+
+ to_nop->opcode = 0;
+}
+
+static void lightrec_remove_useless_lui(struct block *block, unsigned int offset,
+ u32 known, u32 *values)
+{
+ struct opcode *list = block->opcode_list,
+ *op = &block->opcode_list[offset];
+ int reader;
+
+ if (!op_flag_sync(op->flags) && (known & BIT(op->i.rt)) &&
+ values[op->i.rt] == op->i.imm << 16) {
+ pr_debug("Converting duplicated LUI to NOP\n");
+ op->opcode = 0x0;
+ return;
+ }
+
+ if (op->i.imm != 0 || op->i.rt == 0)
+ return;
+
+ reader = find_next_reader(list, offset + 1, op->i.rt);
+ if (reader <= 0)
+ return;
+
+ if (opcode_writes_register(list[reader].c, op->i.rt) ||
+ reg_is_dead(list, reader, op->i.rt)) {
+ pr_debug("Removing useless LUI 0x0\n");
+
+ if (list[reader].i.rs == op->i.rt)
+ list[reader].i.rs = 0;
+ if (list[reader].i.op == OP_SPECIAL &&
+ list[reader].i.rt == op->i.rt)
+ list[reader].i.rt = 0;
+ op->opcode = 0x0;
+ }
+}
+
+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)) {
+ 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;
+ }