#include #include #include #include #include "../color.h" #include "../riscv.h" #include "debug.h" /* Width for opcode field alignment. */ #define OP_WIDTH 6 /* Load instructions based on funct3. */ static const char *load_op_names[] = { "lb", /* FUNCT3_BYTE */ "lh", /* FUNCT3_HALF */ "lw", /* FUNCT3_WORD */ "ld", /* FUNCT3_DOUBLE (RV64) */ "lbu", /* FUNCT3_BYTE_U */ "lhu", /* FUNCT3_HALF_U */ "lwu", /* FUNCT3_WORD_U */ "?" /* invalid */ }; /* Store instructions based on funct3. */ static const char *store_op_names[] = { "sb", /* FUNCT3_BYTE */ "sh", /* FUNCT3_HALF */ "sw", /* FUNCT3_WORD */ "sd", /* FUNCT3_DOUBLE (RV64) */ "?", /* invalid */ "?", /* invalid */ "?", /* invalid */ "?" /* invalid */ }; /* Branch instructions based on funct3. */ static const char *branch_op_names[] = { "beq", /* FUNCT3_BYTE */ "bne", /* FUNCT3_HALF */ "?", /* invalid */ "?", /* invalid */ "blt", /* FUNCT3_BYTE_U */ "bge", /* FUNCT3_HALF_U */ "bltu", /* FUNCT3_OR */ "bgeu" /* FUNCT3_AND */ }; /* ALU operations with immediates based on funct3. */ static const char *alu_imm_op_names[] = { "addi", /* FUNCT3_ADD */ "slli", /* FUNCT3_SLL */ "slti", /* FUNCT3_SLT */ "sltiu", /* FUNCT3_SLTU */ "xori", /* FUNCT3_XOR */ "srli", /* FUNCT3_SRL (may also be SRAI) */ "ori", /* FUNCT3_OR */ "andi" /* FUNCT3_AND */ }; /* ALU operations (register-register) based on funct3 and funct7. * Index as `[funct7][funct3]` */ static const char *alu_op_names[4][8] = { /* FUNCT7_NORMAL (0x00) */ { "add", /* FUNCT3_ADD */ "sll", /* FUNCT3_SLL */ "slt", /* FUNCT3_SLT */ "sltu", /* FUNCT3_SLTU */ "xor", /* FUNCT3_XOR */ "srl", /* FUNCT3_SRL */ "or", /* FUNCT3_OR */ "and" /* FUNCT3_AND */ }, /* FUNCT7_SUB (0x20) - Subset of instructions that use this funct7 */ { "sub", /* FUNCT3_ADD */ "?", /* invalid */ "?", /* invalid */ "?", /* invalid */ "?", /* invalid */ "sra", /* FUNCT3_SRL */ "?", /* invalid */ "?" /* invalid */ }, /* Not used, placeholder for index 2 */ { "?", "?", "?", "?", "?", "?", "?", "?" }, /* FUNCT7_MUL (0x01) - M-extension instructions */ { "mul", /* FUNCT3_ADD */ "mulh", /* FUNCT3_SLL */ "mulhsu", /* FUNCT3_SLT */ "mulhu", /* FUNCT3_SLTU */ "div", /* FUNCT3_XOR */ "divu", /* FUNCT3_SRL */ "rem", /* FUNCT3_OR */ "remu" /* FUNCT3_AND */ } }; /* Print context. */ struct ctx { bool color; i32 length; char *cursor; }; /* Initialize an empty instruction components structure */ static struct instrparts parts(char type) { struct instrparts components; components.op[0] = '\0'; components.rd[0] = '\0'; components.rs1[0] = '\0'; components.rs2[0] = '\0'; components.imm[0] = '\0'; components.off[0] = '\0'; components.base[0] = '\0'; components.type = type; components.is_pseudo = false; return components; } /* Get immediate value from I-type instruction. */ i32 get_i_imm(instr_t instr) { /* Sign-extend 12-bit immediate to 32-bit. */ return sign_extend(instr.i.imm_11_0, 12); } /* Get immediate value from S-type instruction. */ i32 get_s_imm(instr_t instr) { /* Sign-extend 12-bit immediate. */ u32 imm = (u32)((instr.s.imm_11_5 << 5) | instr.s.imm_4_0); return sign_extend(imm, 12); } /* Get immediate value from B-type instruction. */ i32 get_b_imm(instr_t instr) { u32 imm = (u32)((instr.b.imm_12 << 12) | (instr.b.imm_11 << 11) | (instr.b.imm_10_5 << 5) | (instr.b.imm_4_1 << 1)); return sign_extend(imm, 13); } /* Get immediate value from J-type instruction. */ i32 get_j_imm(instr_t instr) { /* The sign bit is already in bit 20 (imm_20). */ u32 imm = (u32)((instr.j.imm_20 << 20) | (instr.j.imm_19_12 << 12) | (instr.j.imm_11 << 11) | (instr.j.imm_10_1 << 1)); return sign_extend(imm, 21); } /* Map opcodes to decode function and get instruction components. */ struct instrparts instr_decode(instr_t instr) { struct instrparts comp; i32 imm; int funct7_idx; /* Handle `nop` (`addi x0, x0, 0`). */ if (instr.raw == 0x00000013) { comp = parts('?'); strncpy(comp.op, "nop", MAX_PART_LEN); comp.is_pseudo = true; return comp; } /* Decode based on opcode */ switch (instr.i.opcode) { case OP_OP: /* R-type instruction (ADD, SUB, etc.) */ comp = parts('R'); /* Compute lookup index for funct7 */ switch (instr.r.funct7) { case FUNCT7_NORMAL: funct7_idx = 0; break; case FUNCT7_SUB: funct7_idx = 1; break; case FUNCT7_MUL: funct7_idx = 3; break; default: funct7_idx = 0; break; } strncpy( comp.op, alu_op_names[funct7_idx][instr.r.funct3], MAX_PART_LEN ); strncpy(comp.rd, reg_names[instr.r.rd], MAX_PART_LEN); strncpy(comp.rs1, reg_names[instr.r.rs1], MAX_PART_LEN); strncpy(comp.rs2, reg_names[instr.r.rs2], MAX_PART_LEN); break; case OP_IMM: /* I-type ALU instruction (ADDI, SLTI, etc.) */ comp = parts('I'); imm = get_i_imm(instr); /* Special case for `mv` pseudo-instruction */ if (instr.i.funct3 == FUNCT3_ADD && instr.i.imm_11_0 == 0 && instr.i.rs1 != 0) { strncpy(comp.op, "mv", MAX_PART_LEN); strncpy(comp.rd, reg_names[instr.i.rd], MAX_PART_LEN); strncpy(comp.rs1, reg_names[instr.i.rs1], MAX_PART_LEN); comp.is_pseudo = true; return comp; } /* Special case for `li` pseudo-instruction */ if (instr.i.funct3 == FUNCT3_ADD && instr.i.rs1 == 0) { strncpy(comp.op, "li", MAX_PART_LEN); strncpy(comp.rd, reg_names[instr.i.rd], MAX_PART_LEN); snprintf(comp.imm, MAX_PART_LEN, "%d", imm); comp.is_pseudo = true; return comp; } const char *imm_op = alu_imm_op_names[instr.i.funct3]; /* Special case for right shifts (SRAI vs SRLI) */ if (instr.i.funct3 == FUNCT3_SRL && (instr.i.imm_11_0 & 0x400)) { imm_op = "srai"; } strncpy(comp.op, imm_op, MAX_PART_LEN); strncpy(comp.rd, reg_names[instr.i.rd], MAX_PART_LEN); strncpy(comp.rs1, reg_names[instr.i.rs1], MAX_PART_LEN); snprintf(comp.imm, MAX_PART_LEN, "%d", imm); break; case OP_IMM_32: { /* RV64I: 32-bit immediate operations (ADDIW, SLLIW, SRLIW, SRAIW) */ comp = parts('I'); imm = get_i_imm(instr); /* Special case: sext.w pseudo-instruction (addiw rd, rs1, 0) */ if (instr.i.funct3 == FUNCT3_ADD && instr.i.imm_11_0 == 0) { strncpy(comp.op, "sext.w", MAX_PART_LEN); strncpy(comp.rd, reg_names[instr.i.rd], MAX_PART_LEN); strncpy(comp.rs1, reg_names[instr.i.rs1], MAX_PART_LEN); comp.is_pseudo = true; return comp; } const char *w_op; switch (instr.i.funct3) { case FUNCT3_ADD: w_op = "addiw"; break; case FUNCT3_SLL: w_op = "slliw"; break; case FUNCT3_SRL: w_op = (instr.i.imm_11_0 & 0x400) ? "sraiw" : "srliw"; break; default: w_op = "?w"; break; } strncpy(comp.op, w_op, MAX_PART_LEN); strncpy(comp.rd, reg_names[instr.i.rd], MAX_PART_LEN); strncpy(comp.rs1, reg_names[instr.i.rs1], MAX_PART_LEN); snprintf(comp.imm, MAX_PART_LEN, "%d", imm); break; } case OP_OP_32: { /* RV64I: 32-bit register-register operations */ comp = parts('R'); const char *w_rop; switch (instr.r.funct7) { case FUNCT7_NORMAL: switch (instr.r.funct3) { case FUNCT3_ADD: w_rop = "addw"; break; case FUNCT3_SLL: w_rop = "sllw"; break; case FUNCT3_SRL: w_rop = "srlw"; break; default: w_rop = "?w"; break; } break; case FUNCT7_SUB: switch (instr.r.funct3) { case FUNCT3_ADD: w_rop = "subw"; break; case FUNCT3_SRL: w_rop = "sraw"; break; default: w_rop = "?w"; break; } break; case FUNCT7_MUL: switch (instr.r.funct3) { case FUNCT3_ADD: w_rop = "mulw"; break; case FUNCT3_XOR: w_rop = "divw"; break; case FUNCT3_SRL: w_rop = "divuw"; break; case FUNCT3_OR: w_rop = "remw"; break; case FUNCT3_AND: w_rop = "remuw"; break; default: w_rop = "?w"; break; } break; default: w_rop = "?w"; break; } strncpy(comp.op, w_rop, MAX_PART_LEN); strncpy(comp.rd, reg_names[instr.r.rd], MAX_PART_LEN); strncpy(comp.rs1, reg_names[instr.r.rs1], MAX_PART_LEN); strncpy(comp.rs2, reg_names[instr.r.rs2], MAX_PART_LEN); break; } case OP_LOAD: /* I-type Load instruction (LW, LH, etc.) */ comp = parts('I'); imm = get_i_imm(instr); strncpy(comp.op, load_op_names[instr.i.funct3], MAX_PART_LEN); strncpy(comp.rd, reg_names[instr.i.rd], MAX_PART_LEN); snprintf(comp.off, MAX_PART_LEN, "%d", imm); strncpy(comp.base, reg_names[instr.i.rs1], MAX_PART_LEN); break; case OP_JALR: /* I-type JALR instruction */ comp = parts('I'); imm = get_i_imm(instr); /* Special case for `ret` pseudo-instruction */ if (instr.i.rd == 0 && instr.i.rs1 == 1 && instr.i.imm_11_0 == 0) { strncpy(comp.op, "ret", MAX_PART_LEN); comp.is_pseudo = true; return comp; } strncpy(comp.op, "jalr", MAX_PART_LEN); strncpy(comp.rd, reg_names[instr.i.rd], MAX_PART_LEN); snprintf(comp.off, MAX_PART_LEN, "%d", imm); strncpy(comp.base, reg_names[instr.i.rs1], MAX_PART_LEN); break; case OP_STORE: /* S-type instruction (SW, SH, etc.) */ comp = parts('S'); imm = get_s_imm(instr); strncpy(comp.op, store_op_names[instr.s.funct3], MAX_PART_LEN); strncpy(comp.rs2, reg_names[instr.s.rs2], MAX_PART_LEN); snprintf(comp.off, MAX_PART_LEN, "%d", imm); strncpy(comp.base, reg_names[instr.s.rs1], MAX_PART_LEN); break; case OP_BRANCH: /* B-type instruction (BEQ, BNE, etc.) */ comp = parts('B'); imm = get_b_imm(instr); strncpy(comp.op, branch_op_names[instr.b.funct3], MAX_PART_LEN); strncpy(comp.rs1, reg_names[instr.b.rs1], MAX_PART_LEN); strncpy(comp.rs2, reg_names[instr.b.rs2], MAX_PART_LEN); snprintf(comp.imm, MAX_PART_LEN, "%d", imm); break; case OP_LUI: case OP_AUIPC: /* U-type instruction (LUI, AUIPC) */ comp = parts('U'); /* Choose operation name based on opcode */ strncpy( comp.op, (instr.u.opcode == OP_LUI) ? "lui" : "auipc", MAX_PART_LEN ); strncpy(comp.rd, reg_names[instr.u.rd], MAX_PART_LEN); snprintf(comp.imm, MAX_PART_LEN, "0x%x", instr.u.imm_31_12); break; case OP_JAL: /* J-type instruction (JAL) */ comp = parts('J'); imm = get_j_imm(instr); /* J pseudo-instruction (JAL with rd=x0) */ if (instr.j.rd == 0) { strncpy(comp.op, "j", MAX_PART_LEN); snprintf(comp.imm, MAX_PART_LEN, "%d", imm); comp.is_pseudo = true; } else { strncpy(comp.op, "jal", MAX_PART_LEN); strncpy(comp.rd, reg_names[instr.j.rd], MAX_PART_LEN); snprintf(comp.imm, MAX_PART_LEN, "%d", imm); } break; case OP_SYSTEM: /* System instructions */ if (instr.i.imm_11_0 == 0) { /* ECALL instruction */ comp = parts('I'); strncpy(comp.op, "ecall", MAX_PART_LEN); return comp; } else if (instr.i.imm_11_0 == 1) { /* EBREAK instruction */ comp = parts('I'); strncpy(comp.op, "ebreak", MAX_PART_LEN); return comp; } /* Fall through for unknown system instructions */ /* fall through */ case OP_LOAD_FP: /* F Extension - Floating-point load instruction */ comp = parts('I'); imm = get_i_imm(instr); if (instr.i.funct3 == FUNCT3_WORD_FP) { strncpy(comp.op, "flw", MAX_PART_LEN); } else { strncpy( comp.op, "flw", MAX_PART_LEN ); /* Only single precision supported */ } strncpy(comp.rd, reg_names[instr.i.rd], MAX_PART_LEN); snprintf(comp.off, MAX_PART_LEN, "%d", imm); strncpy(comp.base, reg_names[instr.i.rs1], MAX_PART_LEN); break; case OP_STORE_FP: /* F Extension - Floating-point store instruction */ comp = parts('S'); imm = get_s_imm(instr); if (instr.s.funct3 == FUNCT3_WORD_FP) { strncpy(comp.op, "fsw", MAX_PART_LEN); } else { strncpy( comp.op, "fsw", MAX_PART_LEN ); /* Only single precision supported */ } strncpy(comp.rs2, reg_names[instr.s.rs2], MAX_PART_LEN); snprintf(comp.off, MAX_PART_LEN, "%d", imm); strncpy(comp.base, reg_names[instr.s.rs1], MAX_PART_LEN); break; case OP_OP_FP: /* F Extension - Floating-point operation instruction */ comp = parts('R'); switch (instr.r.funct7) { case FUNCT7_FADD_S: strncpy(comp.op, "fadd.s", MAX_PART_LEN); break; case FUNCT7_FSUB_S: strncpy(comp.op, "fsub.s", MAX_PART_LEN); break; case FUNCT7_FMUL_S: strncpy(comp.op, "fmul.s", MAX_PART_LEN); break; case FUNCT7_FDIV_S: strncpy(comp.op, "fdiv.s", MAX_PART_LEN); break; case FUNCT7_FEQ_S: /* Comparison instructions use different funct3 values */ switch (instr.r.funct3) { case FUNCT3_FEQ: strncpy(comp.op, "feq.s", MAX_PART_LEN); break; case FUNCT3_FLT: strncpy(comp.op, "flt.s", MAX_PART_LEN); break; case FUNCT3_FLE: strncpy(comp.op, "fle.s", MAX_PART_LEN); break; default: strncpy(comp.op, "fcmp.s", MAX_PART_LEN); break; } break; default: strncpy(comp.op, "fop.s", MAX_PART_LEN); break; } strncpy(comp.rd, reg_names[instr.r.rd], MAX_PART_LEN); strncpy(comp.rs1, reg_names[instr.r.rs1], MAX_PART_LEN); strncpy(comp.rs2, reg_names[instr.r.rs2], MAX_PART_LEN); break; default: /* Unknown opcode */ comp = parts('?'); snprintf(comp.op, MAX_PART_LEN, "unknown (%d)", instr.i.opcode); break; } return comp; } /* Helper function to format colored text with automatic reset. * When color is COLOR_NORMAL, outputs plain text with no color codes */ static void put(struct ctx *ctx, const char *color, const char *str) { if (color && ctx->color) { ctx->cursor += sprintf(ctx->cursor, "%s", color); } i32 len = sprintf(ctx->cursor, "%s", str); ctx->cursor += len; ctx->length += len; if (color && ctx->color) { ctx->cursor += sprintf(ctx->cursor, "%s", COLOR_RESET); } } /* Format instruction components to a string. * If use_color is true, the output will include ANSI color codes. */ static void format_instr(const struct instrparts *i, struct ctx *ctx) { /* Define colors based on whether color is enabled */ const char *opcolor = COLOR_BOLD; const char *rdcolor = COLOR_GREEN; const char *rscolor = COLOR_GREEN; const char *immcolor = COLOR_BLUE; const char *nocolor = COLOR_NORMAL; put(ctx, opcolor, i->op); /* Check if this is an op without operands. */ if (i->is_pseudo && (strcmp(i->op, "ret") == 0 || strcmp(i->op, "nop") == 0)) { return; } if (strcmp(i->op, "ebreak") == 0 || strcmp(i->op, "ecall") == 0) return; /* Padding for ops with operands */ for (usize len = strlen(i->op); len <= OP_WIDTH; len++) put(ctx, nocolor, " "); if (i->is_pseudo) { if (strcmp(i->op, "j") == 0) { put(ctx, nocolor, " "); put(ctx, immcolor, i->imm); return; } else if (strcmp(i->op, "mv") == 0) { put(ctx, nocolor, " "); put(ctx, rdcolor, i->rd); put(ctx, nocolor, ", "); put(ctx, rscolor, i->rs1); return; } else if (strcmp(i->op, "li") == 0) { put(ctx, nocolor, " "); put(ctx, rdcolor, i->rd); put(ctx, nocolor, ", "); put(ctx, immcolor, i->imm); return; } } switch (i->type) { case 'R': /* R-type: op rd, rs1, rs2. */ put(ctx, nocolor, " "); put(ctx, rdcolor, i->rd); put(ctx, nocolor, ", "); put(ctx, rscolor, i->rs1); put(ctx, nocolor, ", "); put(ctx, rscolor, i->rs2); break; case 'I': if (i->base[0] != '\0') { /* I-type (load/jalr): op rd, offset(rs1). */ put(ctx, nocolor, " "); put(ctx, rdcolor, i->rd); put(ctx, nocolor, ", "); put(ctx, immcolor, i->off); put(ctx, nocolor, "("); put(ctx, rscolor, i->base); put(ctx, nocolor, ")"); } else { /* I-type (immediate): op rd, rs1, imm. */ put(ctx, nocolor, " "); put(ctx, rdcolor, i->rd); put(ctx, nocolor, ", "); put(ctx, rscolor, i->rs1); put(ctx, nocolor, ", "); put(ctx, immcolor, i->imm); } break; case 'S': /* S-type: op rs2, offset(rs1). */ put(ctx, nocolor, " "); put(ctx, rscolor, i->rs2); put(ctx, nocolor, ", "); put(ctx, immcolor, i->off); put(ctx, nocolor, "("); put(ctx, rscolor, i->base); put(ctx, nocolor, ")"); break; case 'B': /* B-type: op rs1, rs2, imm. */ put(ctx, nocolor, " "); put(ctx, rscolor, i->rs1); put(ctx, nocolor, ", "); put(ctx, rscolor, i->rs2); put(ctx, nocolor, ", "); put(ctx, immcolor, i->imm); break; case 'U': /* U-type: op rd, imm. */ put(ctx, nocolor, " "); put(ctx, rdcolor, i->rd); put(ctx, nocolor, ", "); put(ctx, immcolor, i->imm); break; case 'J': /* J-type: op rd, imm. */ put(ctx, nocolor, " "); if (i->rd[0] != '\0') { put(ctx, rdcolor, i->rd); put(ctx, nocolor, ", "); put(ctx, immcolor, i->imm); } else { put(ctx, immcolor, i->imm); } break; default: /* Unknown type or invalid opcode. */ break; } } i32 sprint_instr(instr_t instr, char *buf, bool color) { struct ctx ctx = (struct ctx){ .color = color, .length = 0, .cursor = buf }; struct instrparts parts = instr_decode(instr); format_instr(&parts, &ctx); return ctx.length; } void print_instr( instr_t instr, FILE *stream, const char *comment, bool color, i32 indent ) { char buf[MAX_INSTR_STR_LEN] = { 0 }; i32 n = sprint_instr(instr, buf, color); color = color && isatty(stream->_fileno); /* Indent line */ fprintf(stream, "%*s", indent, " "); if (comment) { fprintf(stream, "%s", buf); fprintf(stream, "%-*s", INSTR_STR_LEN - n, ""); if (color) { fputs(COLOR_GREY, stream); } fprintf(stream, "# %s\n", comment); if (color) { fputs(COLOR_RESET, stream); } } else { fprintf(stream, "%s\n", buf); } } void print_instrs( instr_t *instrs, usize count, FILE *stream, const char *comment ) { bool color = (bool)isatty(stream->_fileno); for (usize i = 0; i < count; i++) { char buf[MAX_INSTR_STR_LEN]; usize addr = i * INSTR_SIZE; sprint_instr(instrs[i], buf, color); if (comment) { fprintf( stream, "%04lx: %08x %s # %s\n", addr, instrs[i].raw, buf, comment ); } else { fprintf(stream, "%04lx: %08x %s\n", addr, instrs[i].raw, buf); } } }