git subrepo pull --force deps/lightrec
[pcsx_rearmed.git] / deps / lightrec / disassembler.c
index 06fcec9..cb332c6 100644 (file)
+// SPDX-License-Identifier: LGPL-2.1-or-later
 /*
- * Copyright (C) 2014-2020 Paul Cercueil <paul@crapouillou.net>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
+ * Copyright (C) 2014-2021 Paul Cercueil <paul@crapouillou.net>
  */
 
-#include "config.h"
-
-#if ENABLE_DISASSEMBLER
-#include <dis-asm.h>
-#endif
 #include <stdbool.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
-#include "debug.h"
-#include "disassembler.h"
 #include "lightrec-private.h"
-#include "memmanager.h"
+#include "regcache.h"
+
+static const char *std_opcodes[] = {
+       [OP_J]                  = "j       ",
+       [OP_JAL]                = "jal     ",
+       [OP_BEQ]                = "beq     ",
+       [OP_BNE]                = "bne     ",
+       [OP_BLEZ]               = "blez    ",
+       [OP_BGTZ]               = "bgtz    ",
+       [OP_ADDI]               = "addi    ",
+       [OP_ADDIU]              = "addiu   ",
+       [OP_SLTI]               = "slti    ",
+       [OP_SLTIU]              = "sltiu   ",
+       [OP_ANDI]               = "andi    ",
+       [OP_ORI]                = "ori     ",
+       [OP_XORI]               = "xori    ",
+       [OP_LUI]                = "lui     ",
+       [OP_LB]                 = "lb      ",
+       [OP_LH]                 = "lh      ",
+       [OP_LWL]                = "lwl     ",
+       [OP_LW]                 = "lw      ",
+       [OP_LBU]                = "lbu     ",
+       [OP_LHU]                = "lhu     ",
+       [OP_LWR]                = "lwr     ",
+       [OP_SB]                 = "sb      ",
+       [OP_SH]                 = "sh      ",
+       [OP_SWL]                = "swl     ",
+       [OP_SW]                 = "sw      ",
+       [OP_SWR]                = "swr     ",
+       [OP_LWC2]               = "lwc2    ",
+       [OP_SWC2]               = "swc2    ",
+};
+
+static const char *special_opcodes[] = {
+       [OP_SPECIAL_SLL]        = "sll     ",
+       [OP_SPECIAL_SRL]        = "srl     ",
+       [OP_SPECIAL_SRA]        = "sra     ",
+       [OP_SPECIAL_SLLV]       = "sllv    ",
+       [OP_SPECIAL_SRLV]       = "srlv    ",
+       [OP_SPECIAL_SRAV]       = "srav    ",
+       [OP_SPECIAL_JR]         = "jr      ",
+       [OP_SPECIAL_JALR]       = "jalr    ",
+       [OP_SPECIAL_SYSCALL]    = "syscall ",
+       [OP_SPECIAL_BREAK]      = "break   ",
+       [OP_SPECIAL_MFHI]       = "mfhi    ",
+       [OP_SPECIAL_MTHI]       = "mthi    ",
+       [OP_SPECIAL_MFLO]       = "mflo    ",
+       [OP_SPECIAL_MTLO]       = "mtlo    ",
+       [OP_SPECIAL_MULT]       = "mult    ",
+       [OP_SPECIAL_MULTU]      = "multu   ",
+       [OP_SPECIAL_DIV]        = "div     ",
+       [OP_SPECIAL_DIVU]       = "divu    ",
+       [OP_SPECIAL_ADD]        = "add     ",
+       [OP_SPECIAL_ADDU]       = "addu    ",
+       [OP_SPECIAL_SUB]        = "sub     ",
+       [OP_SPECIAL_SUBU]       = "subu    ",
+       [OP_SPECIAL_AND]        = "and     ",
+       [OP_SPECIAL_OR]         = "or      ",
+       [OP_SPECIAL_XOR]        = "xor     ",
+       [OP_SPECIAL_NOR]        = "nor     ",
+       [OP_SPECIAL_SLT]        = "slt     ",
+       [OP_SPECIAL_SLTU]       = "sltu    ",
+};
+
+static const char *regimm_opcodes[] = {
+       [OP_REGIMM_BLTZ]        = "bltz    ",
+       [OP_REGIMM_BGEZ]        = "bgez    ",
+       [OP_REGIMM_BLTZAL]      = "bltzal  ",
+       [OP_REGIMM_BGEZAL]      = "bgezal  ",
+};
+
+static const char *cp0_opcodes[] = {
+       [OP_CP0_MFC0]           = "mfc0    ",
+       [OP_CP0_CFC0]           = "cfc0    ",
+       [OP_CP0_MTC0]           = "mtc0    ",
+       [OP_CP0_CTC0]           = "ctc0    ",
+       [OP_CP0_RFE]            = "rfe",
+};
+
+static const char *cp2_opcodes[] = {
+       [OP_CP2_BASIC_MFC2]     = "mfc2    ",
+       [OP_CP2_BASIC_CFC2]     = "cfc2    ",
+       [OP_CP2_BASIC_MTC2]     = "mtc2    ",
+       [OP_CP2_BASIC_CTC2]     = "ctc2    ",
+};
+
+static const char *opcode_flags[] = {
+       "switched branch/DS",
+       "unload Rs",
+       "unload Rt",
+       "unload Rd",
+       "sync point",
+};
 
-static bool is_unconditional_jump(const struct opcode *op)
+static const char *opcode_io_flags[] = {
+       "self-modifying code",
+       "no invalidation",
+       "no mask",
+};
+
+static const char *opcode_io_modes[] = {
+       "Memory access",
+       "I/O access",
+       "RAM access",
+       "BIOS access",
+       "Scratchpad access",
+};
+
+static const char *opcode_branch_flags[] = {
+       "emulate branch",
+       "local branch",
+};
+
+static const char *opcode_multdiv_flags[] = {
+       "No LO",
+       "No HI",
+       "No div check",
+};
+
+static int print_flags(char *buf, size_t len, u16 flags,
+                      const char **array, size_t array_size,
+                      bool is_io)
 {
-       switch (op->i.op) {
-       case OP_SPECIAL:
-               return op->r.op == OP_SPECIAL_JR || op->r.op == OP_SPECIAL_JALR;
-       case OP_J:
-       case OP_JAL:
-               return true;
-       case OP_BEQ:
-       case OP_BLEZ:
-               return op->i.rs == op->i.rt;
-       case OP_REGIMM:
-               return (op->r.rt == OP_REGIMM_BGEZ ||
-                       op->r.rt == OP_REGIMM_BGEZAL) && op->i.rs == 0;
-       default:
-               return false;
+       const char *flag_name, *io_mode_name;
+       unsigned int i, io_mode;
+       size_t count = 0, bytes;
+       bool first = true;
+
+       for (i = 0; i < array_size + ARRAY_SIZE(opcode_flags); i++) {
+               if (!(flags & BIT(i)))
+                       continue;
+
+               if (i < ARRAY_SIZE(opcode_flags))
+                       flag_name = opcode_flags[i];
+               else
+                       flag_name = array[i - ARRAY_SIZE(opcode_flags)];
+
+               if (first)
+                       bytes = snprintf(buf, len, "(%s", flag_name);
+               else
+                       bytes = snprintf(buf, len, ", %s", flag_name);
+
+               first = false;
+               buf += bytes;
+               len -= bytes;
+               count += bytes;
        }
-}
 
-static bool is_syscall(const struct opcode *op)
-{
-       return (op->i.op == OP_SPECIAL && (op->r.op == OP_SPECIAL_SYSCALL ||
-                                          op->r.op == OP_SPECIAL_BREAK)) ||
-               (op->i.op == OP_CP0 && (op->r.rs == OP_CP0_MTC0 ||
-                                       op->r.rs == OP_CP0_CTC0) &&
-                (op->r.rd == 12 || op->r.rd == 13));
-}
+       if (is_io) {
+               io_mode = LIGHTREC_FLAGS_GET_IO_MODE(flags);
+               if (io_mode > 0) {
+                       io_mode_name = opcode_io_modes[io_mode - 1];
 
-void lightrec_free_opcode_list(struct lightrec_state *state, struct opcode *list)
-{
-       struct opcode *next;
+                       if (first)
+                               bytes = snprintf(buf, len, "(%s", io_mode_name);
+                       else
+                               bytes = snprintf(buf, len, ", %s", io_mode_name);
 
-       while (list) {
-               next = list->next;
-               lightrec_free(state, MEM_FOR_IR, sizeof(*list), list);
-               list = next;
+                       first = false;
+                       buf += bytes;
+                       len -= bytes;
+                       count += bytes;
+               }
        }
+
+       if (!first)
+               count += snprintf(buf, len, ")");
+       else
+               *buf = '\0';
+
+       return count;
 }
 
-struct opcode * lightrec_disassemble(struct lightrec_state *state,
-                                    const u32 *src, unsigned int *len)
+static int print_op_special(union code c, char *buf, size_t len,
+                           const char ***flags_ptr, size_t *nb_flags)
 {
-       struct opcode *head = NULL;
-       bool stop_next = false;
-       struct opcode *curr, *last;
-       unsigned int i;
+       switch (c.r.op) {
+       case OP_SPECIAL_SLL:
+       case OP_SPECIAL_SRL:
+       case OP_SPECIAL_SRA:
+               return snprintf(buf, len, "%s%s,%s,%u",
+                               special_opcodes[c.r.op],
+                               lightrec_reg_name(c.r.rd),
+                               lightrec_reg_name(c.r.rt),
+                               c.r.imm);
+       case OP_SPECIAL_SLLV:
+       case OP_SPECIAL_SRLV:
+       case OP_SPECIAL_SRAV:
+       case OP_SPECIAL_ADD:
+       case OP_SPECIAL_ADDU:
+       case OP_SPECIAL_SUB:
+       case OP_SPECIAL_SUBU:
+       case OP_SPECIAL_AND:
+       case OP_SPECIAL_OR:
+       case OP_SPECIAL_XOR:
+       case OP_SPECIAL_NOR:
+       case OP_SPECIAL_SLT:
+       case OP_SPECIAL_SLTU:
+               return snprintf(buf, len, "%s%s,%s,%s",
+                               special_opcodes[c.r.op],
+                               lightrec_reg_name(c.r.rd),
+                               lightrec_reg_name(c.r.rt),
+                               lightrec_reg_name(c.r.rs));
+       case OP_SPECIAL_JR:
+       case OP_SPECIAL_MTHI:
+       case OP_SPECIAL_MTLO:
+               return snprintf(buf, len, "%s%s",
+                               special_opcodes[c.r.op],
+                               lightrec_reg_name(c.r.rs));
+       case OP_SPECIAL_JALR:
+               return snprintf(buf, len, "%s%s,%s",
+                               special_opcodes[c.r.op],
+                               lightrec_reg_name(c.r.rd),
+                               lightrec_reg_name(c.r.rt));
+       case OP_SPECIAL_SYSCALL:
+       case OP_SPECIAL_BREAK:
+               return snprintf(buf, len, "%s", special_opcodes[c.r.op]);
+       case OP_SPECIAL_MFHI:
+       case OP_SPECIAL_MFLO:
+               return snprintf(buf, len, "%s%s",
+                               special_opcodes[c.r.op],
+                               lightrec_reg_name(c.r.rd));
+       case OP_SPECIAL_MULT:
+       case OP_SPECIAL_MULTU:
+       case OP_SPECIAL_DIV:
+       case OP_SPECIAL_DIVU:
+               *flags_ptr = opcode_multdiv_flags;
+               *nb_flags = ARRAY_SIZE(opcode_multdiv_flags);
+               return snprintf(buf, len, "%s%s,%s,%s,%s",
+                               special_opcodes[c.r.op],
+                               lightrec_reg_name(get_mult_div_hi(c)),
+                               lightrec_reg_name(get_mult_div_lo(c)),
+                               lightrec_reg_name(c.r.rs),
+                               lightrec_reg_name(c.r.rt));
+       default:
+               return snprintf(buf, len, "unknown (0x%08x)", c.opcode);
+       }
+}
 
-       for (i = 0, last = NULL; ; i++, last = curr) {
-               curr = lightrec_calloc(state, MEM_FOR_IR, sizeof(*curr));
-               if (!curr) {
-                       pr_err("Unable to allocate memory\n");
-                       lightrec_free_opcode_list(state, head);
-                       return NULL;
+static int print_op_cp(union code c, char *buf, size_t len, unsigned int cp)
+{
+       if (cp == 2) {
+               switch (c.i.rs) {
+               case OP_CP0_MFC0:
+               case OP_CP0_CFC0:
+               case OP_CP0_MTC0:
+               case OP_CP0_CTC0:
+                       return snprintf(buf, len, "%s%s,%u",
+                                       cp2_opcodes[c.i.rs],
+                                       lightrec_reg_name(c.i.rt),
+                                       c.r.rd);
+               default:
+                       return snprintf(buf, len, "cp2     (0x%08x)", c.opcode);
+               }
+       } else {
+               switch (c.i.rs) {
+               case OP_CP0_MFC0:
+               case OP_CP0_CFC0:
+               case OP_CP0_MTC0:
+               case OP_CP0_CTC0:
+                       return snprintf(buf, len, "%s%s,%u",
+                                       cp0_opcodes[c.i.rs],
+                                       lightrec_reg_name(c.i.rt),
+                                       c.r.rd);
+               case OP_CP0_RFE:
+                       return snprintf(buf, len, "rfe     ");
+               default:
+                       return snprintf(buf, len, "unknown (0x%08x)", c.opcode);
                }
-
-               if (!last)
-                       head = curr;
-               else
-                       last->next = curr;
-
-               /* TODO: Take care of endianness */
-               curr->opcode = LE32TOH(*src++);
-               curr->offset = i;
-
-               /* NOTE: The block disassembly ends after the opcode that
-                * follows an unconditional jump (delay slot) */
-               if (stop_next || is_syscall(curr))
-                       break;
-               else if (is_unconditional_jump(curr))
-                       stop_next = true;
        }
-
-       if (len)
-               *len = (i + 1) * sizeof(u32);
-
-       return head;
 }
 
-unsigned int lightrec_cycles_of_opcode(union code code)
+static int print_op(union code c, u32 pc, char *buf, size_t len,
+                   const char ***flags_ptr, size_t *nb_flags,
+                   bool *is_io)
 {
-       switch (code.i.op) {
-       case OP_META_REG_UNLOAD:
-       case OP_META_SYNC:
-               return 0;
+       if (c.opcode == 0)
+               return snprintf(buf, len, "nop     ");
+
+       switch (c.i.op) {
+       case OP_SPECIAL:
+               return print_op_special(c, buf, len, flags_ptr, nb_flags);
+       case OP_REGIMM:
+               *flags_ptr = opcode_branch_flags;
+               *nb_flags = ARRAY_SIZE(opcode_branch_flags);
+               return snprintf(buf, len, "%s%s,0x%x",
+                               regimm_opcodes[c.i.rt],
+                               lightrec_reg_name(c.i.rs),
+                               pc + 4 + ((s16)c.i.imm << 2));
+       case OP_J:
+       case OP_JAL:
+               return snprintf(buf, len, "%s0x%x",
+                               std_opcodes[c.i.op],
+                               (pc & 0xf0000000) | (c.j.imm << 2));
+       case OP_BEQ:
+       case OP_BNE:
+       case OP_BLEZ:
+       case OP_BGTZ:
+               *flags_ptr = opcode_branch_flags;
+               *nb_flags = ARRAY_SIZE(opcode_branch_flags);
+               return snprintf(buf, len, "%s%s,%s,0x%x",
+                               std_opcodes[c.i.op],
+                               lightrec_reg_name(c.i.rs),
+                               lightrec_reg_name(c.i.rt),
+                               pc + 4 + ((s16)c.i.imm << 2));
+       case OP_ADDI:
+       case OP_ADDIU:
+       case OP_SLTI:
+       case OP_SLTIU:
+       case OP_ANDI:
+       case OP_ORI:
+       case OP_XORI:
+               return snprintf(buf, len, "%s%s,%s,0x%04hx",
+                               std_opcodes[c.i.op],
+                               lightrec_reg_name(c.i.rt),
+                               lightrec_reg_name(c.i.rs),
+                               (u16)c.i.imm);
+
+       case OP_LUI:
+               return snprintf(buf, len, "%s%s,0x%04hx",
+                               std_opcodes[c.i.op],
+                               lightrec_reg_name(c.i.rt),
+                               (u16)c.i.imm);
+       case OP_CP0:
+               return print_op_cp(c, buf, len, 0);
+       case OP_CP2:
+               return print_op_cp(c, buf, len, 2);
+       case OP_LB:
+       case OP_LH:
+       case OP_LWL:
+       case OP_LW:
+       case OP_LBU:
+       case OP_LHU:
+       case OP_LWR:
+       case OP_SB:
+       case OP_SH:
+       case OP_SWL:
+       case OP_SW:
+       case OP_SWR:
+               *flags_ptr = opcode_io_flags;
+               *nb_flags = ARRAY_SIZE(opcode_io_flags);
+               *is_io = true;
+               return snprintf(buf, len, "%s%s,%hd(%s)",
+                               std_opcodes[c.i.op],
+                               lightrec_reg_name(c.i.rt),
+                               (s16)c.i.imm,
+                               lightrec_reg_name(c.i.rs));
+       case OP_LWC2:
+       case OP_SWC2:
+               *flags_ptr = opcode_io_flags;
+               *nb_flags = ARRAY_SIZE(opcode_io_flags);
+               return snprintf(buf, len, "%s%s,%hd(%s)",
+                               std_opcodes[c.i.op],
+                               lightrec_reg_name(c.i.rt),
+                               (s16)c.i.imm,
+                               lightrec_reg_name(c.i.rs));
+       case OP_META_MOV:
+               return snprintf(buf, len, "move    %s,%s",
+                               lightrec_reg_name(c.r.rd),
+                               lightrec_reg_name(c.r.rs));
+       case OP_META_EXTC:
+               return snprintf(buf, len, "extc    %s,%s",
+                               lightrec_reg_name(c.i.rt),
+                               lightrec_reg_name(c.i.rs));
+       case OP_META_EXTS:
+               return snprintf(buf, len, "exts    %s,%s",
+                               lightrec_reg_name(c.i.rt),
+                               lightrec_reg_name(c.i.rs));
        default:
-               return 2;
+               return snprintf(buf, len, "unknown (0x%08x)", c.opcode);
        }
 }
 
-#if ENABLE_DISASSEMBLER
-void lightrec_print_disassembly(const struct block *block,
-                               const u32 *code, unsigned int length)
+void lightrec_print_disassembly(const struct block *block, const u32 *code_ptr)
 {
-       struct disassemble_info info;
+       const struct opcode *op;
+       const char **flags_ptr;
+       size_t nb_flags, count, count2;
+       char buf[256], buf2[256], buf3[256];
        unsigned int i;
+       u32 pc, branch_pc, code;
+       bool is_io;
+
+       for (i = 0; i < block->nb_ops; i++) {
+               op = &block->opcode_list[i];
+               branch_pc = get_branch_pc(block, i, 0);
+               pc = block->pc + (i << 2);
+               code = LE32TOH(code_ptr[i]);
+
+               count = print_op((union code)code, pc, buf, sizeof(buf),
+                                &flags_ptr, &nb_flags, &is_io);
+
+               flags_ptr = NULL;
+               nb_flags = 0;
+               is_io = false;
+               count2 = print_op(op->c, branch_pc, buf2, sizeof(buf2),
+                                 &flags_ptr, &nb_flags, &is_io);
+
+               if (code == op->c.opcode) {
+                       *buf2 = '\0';
+                       count2 = 0;
+               }
+
+               print_flags(buf3, sizeof(buf3), op->flags, flags_ptr, nb_flags,
+                           is_io);
 
-       memset(&info, 0, sizeof(info));
-       init_disassemble_info(&info, stdout, (fprintf_ftype) fprintf);
-
-       info.buffer = (bfd_byte *) code;
-       info.buffer_vma = (bfd_vma)(uintptr_t) code;
-       info.buffer_length = length;
-       info.flavour = bfd_target_unknown_flavour;
-       info.arch = bfd_arch_mips;
-       info.mach = bfd_mach_mips3000;
-       disassemble_init_for_target(&info);
-
-       for (i = 0; i < length; i += 4) {
-               void print_insn_little_mips(bfd_vma, struct disassemble_info *);
-               putc('\t', stdout);
-               print_insn_little_mips((bfd_vma)(uintptr_t) code++, &info);
-               putc('\n', stdout);
+               printf("0x%08x (0x%x)\t%s%*c%s%*c%s\n", pc, i << 2,
+                      buf, 30 - (int)count, ' ', buf2, 30 - (int)count2, ' ', buf3);
        }
 }
-#endif