+ }
+
+ reg_lo = get_mfhi_mflo_reg(block, i + 1, NULL, 0, false, true, false);
+ if (reg_lo == 0) {
+ pr_debug("Mark MULT(U)/DIV(U) opcode at offset 0x%x as"
+ " not writing LO\n", i << 2);
+ list->flags |= LIGHTREC_NO_LO;
+ }
+
+ reg_hi = get_mfhi_mflo_reg(block, i + 1, NULL, 0, false, false, false);
+ if (reg_hi == 0) {
+ pr_debug("Mark MULT(U)/DIV(U) opcode at offset 0x%x as"
+ " not writing HI\n", i << 2);
+ list->flags |= LIGHTREC_NO_HI;
+ }
+
+ if (!reg_lo && !reg_hi) {
+ pr_debug("Both LO/HI unused in this block, they will "
+ "probably be used in parent block - removing "
+ "flags.\n");
+ list->flags &= ~(LIGHTREC_NO_LO | LIGHTREC_NO_HI);
+ }
+
+ if (reg_lo > 0 && reg_lo != REG_LO) {
+ pr_debug("Found register %s to hold LO (rs = %u, rt = %u)\n",
+ lightrec_reg_name(reg_lo), list->r.rs, list->r.rt);
+
+ lightrec_replace_lo_hi(block, i + 1, block->nb_ops, true);
+ list->r.rd = reg_lo;
+ } else {
+ list->r.rd = 0;
+ }
+
+ if (reg_hi > 0 && reg_hi != REG_HI) {
+ pr_debug("Found register %s to hold HI (rs = %u, rt = %u)\n",
+ lightrec_reg_name(reg_hi), list->r.rs, list->r.rt);
+
+ lightrec_replace_lo_hi(block, i + 1, block->nb_ops, false);
+ list->r.imm = reg_hi;
+ } else {
+ list->r.imm = 0;
+ }
+ }
+
+ return 0;
+}
+
+static bool remove_div_sequence(struct block *block, unsigned int offset)
+{
+ struct opcode *op;
+ unsigned int i, found = 0;
+
+ /*
+ * Scan for the zero-checking sequence that GCC automatically introduced
+ * after most DIV/DIVU opcodes. This sequence checks the value of the
+ * divisor, and if zero, executes a BREAK opcode, causing the BIOS
+ * handler to crash the PS1.
+ *
+ * For DIV opcodes, this sequence additionally checks that the signed
+ * operation does not overflow.
+ *
+ * With the assumption that the games never crashed the PS1, we can
+ * therefore assume that the games never divided by zero or overflowed,
+ * and these sequences can be removed.
+ */
+
+ for (i = offset; i < block->nb_ops; i++) {
+ op = &block->opcode_list[i];
+
+ if (!found) {
+ if (op->i.op == OP_SPECIAL &&
+ (op->r.op == OP_SPECIAL_DIV || op->r.op == OP_SPECIAL_DIVU))
+ break;
+
+ if ((op->opcode & 0xfc1fffff) == 0x14000002) {
+ /* BNE ???, zero, +8 */
+ found++;
+ } else {
+ offset++;
+ }
+ } else if (found == 1 && !op->opcode) {
+ /* NOP */
+ found++;
+ } else if (found == 2 && op->opcode == 0x0007000d) {
+ /* BREAK 0x1c00 */
+ found++;
+ } else if (found == 3 && op->opcode == 0x2401ffff) {
+ /* LI at, -1 */
+ found++;
+ } else if (found == 4 && (op->opcode & 0xfc1fffff) == 0x14010004) {
+ /* BNE ???, at, +16 */
+ found++;
+ } else if (found == 5 && op->opcode == 0x3c018000) {
+ /* LUI at, 0x8000 */
+ found++;
+ } else if (found == 6 && (op->opcode & 0x141fffff) == 0x14010002) {
+ /* BNE ???, at, +16 */
+ found++;
+ } else if (found == 7 && !op->opcode) {
+ /* NOP */
+ found++;
+ } else if (found == 8 && op->opcode == 0x0006000d) {
+ /* BREAK 0x1800 */
+ found++;
+ break;
+ } else {
+ break;
+ }
+ }
+
+ if (found >= 3) {
+ if (found != 9)
+ found = 3;
+
+ pr_debug("Removing DIV%s sequence at offset 0x%x\n",
+ found == 9 ? "" : "U", offset << 2);
+
+ for (i = 0; i < found; i++)
+ block->opcode_list[offset + i].opcode = 0;
+
+ return true;
+ }
+
+ return false;
+}
+
+static int lightrec_remove_div_by_zero_check_sequence(struct lightrec_state *state,
+ struct block *block)
+{
+ struct opcode *op;
+ unsigned int i;
+
+ for (i = 0; i < block->nb_ops; i++) {
+ op = &block->opcode_list[i];
+
+ if (op->i.op == OP_SPECIAL &&
+ (op->r.op == OP_SPECIAL_DIVU || op->r.op == OP_SPECIAL_DIV) &&
+ remove_div_sequence(block, i + 1))
+ op->flags |= LIGHTREC_NO_DIV_CHECK;
+ }
+
+ return 0;
+}
+
+static const u32 memset_code[] = {
+ 0x10a00006, // beqz a1, 2f
+ 0x24a2ffff, // addiu v0,a1,-1
+ 0x2403ffff, // li v1,-1
+ 0xac800000, // 1: sw zero,0(a0)
+ 0x2442ffff, // addiu v0,v0,-1
+ 0x1443fffd, // bne v0,v1, 1b
+ 0x24840004, // addiu a0,a0,4
+ 0x03e00008, // 2: jr ra
+ 0x00000000, // nop
+};
+
+static int lightrec_replace_memset(struct lightrec_state *state, struct block *block)
+{
+ unsigned int i;
+ union code c;
+
+ for (i = 0; i < block->nb_ops; i++) {
+ c = block->opcode_list[i].c;
+
+ if (c.opcode != memset_code[i])
+ return 0;
+
+ if (i == ARRAY_SIZE(memset_code) - 1) {
+ /* success! */
+ pr_debug("Block at PC 0x%x is a memset\n", block->pc);
+ block_set_flags(block,
+ BLOCK_IS_MEMSET | BLOCK_NEVER_COMPILE);
+
+ /* Return non-zero to skip other optimizers. */
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int lightrec_test_preload_pc(struct lightrec_state *state, struct block *block)
+{
+ unsigned int i;
+ union code c;
+ u32 flags;
+
+ for (i = 0; i < block->nb_ops; i++) {
+ c = block->opcode_list[i].c;
+ flags = block->opcode_list[i].flags;
+
+ if (op_flag_sync(flags))
+ break;
+
+ switch (c.i.op) {
+ case OP_J:
+ case OP_JAL:
+ block->flags |= BLOCK_PRELOAD_PC;
+ return 0;
+
+ case OP_REGIMM:
+ switch (c.r.rt) {
+ case OP_REGIMM_BLTZAL:
+ case OP_REGIMM_BGEZAL:
+ block->flags |= BLOCK_PRELOAD_PC;
+ return 0;
+ default:
+ break;
+ }
+ fallthrough;
+ case OP_BEQ:
+ case OP_BNE:
+ case OP_BLEZ:
+ case OP_BGTZ:
+ if (!op_flag_local_branch(flags)) {
+ block->flags |= BLOCK_PRELOAD_PC;
+ return 0;
+ }