Skip to main content

disc_v/
format.rs

1// Copyright (c) 2026 Christopher Swenson
2// Licensed under the Apache-2.0 license.
3//
4// Derived from riscv-disassembler, Copyright (c) 2016-2017 Michael Clark
5// and Copyright (c) 2017-2018 SiFive, Inc., under the MIT license; see NOTICE.
6
7//! Text output for decoded instructions, in the style of GNU objdump.
8
9use core::fmt::{self, Display, Write};
10
11use crate::csr::csr_name;
12use crate::instruction::Instruction;
13use crate::opcodes::zfa::FLI_CONSTANTS;
14use crate::reg::{FP_NAMES, INT_NAMES};
15
16impl Display for Instruction {
17    /// Formats the instruction as objdump does, with a single space between
18    /// the mnemonic and the operands: `lw a0,8(sp)`.
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        // Some names include an ordering (lw.aq); it is shown with the other
21        // ordering bit (lw.aqrl) from the encoding.
22        let name = self.op.name;
23        let name = match (self.aq || self.rl)
24            .then(|| name.strip_suffix(".aq").or(name.strip_suffix(".rl")))
25        {
26            Some(Some(base)) => base,
27            _ => name,
28        };
29        f.write_str(name)?;
30        match (self.aq, self.rl) {
31            (true, true) => f.write_str(".aqrl")?,
32            (true, false) => f.write_str(".aq")?,
33            (false, true) => f.write_str(".rl")?,
34            (false, false) => {}
35        }
36        if !self.op.format.is_empty() {
37            write!(f, " {}", self.operands())?;
38        }
39        Ok(())
40    }
41}
42
43/// The operands of an instruction, formatted as objdump does: `a0,8(sp)`.
44///
45/// Create one with [`Instruction::operands`].
46#[derive(Clone, Copy, Debug)]
47pub struct Operands<'a>(pub(crate) &'a Instruction);
48
49impl Display for Operands<'_> {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        let ins = self.0;
52        for c in ins.op.format.chars() {
53            write_part(f, ins, c)?;
54        }
55        Ok(())
56    }
57}
58
59/// Writes the output for format character `c`; see
60/// [`fmt`](crate::opcodes::fmt).
61fn write_part(out: &mut fmt::Formatter<'_>, ins: &Instruction, c: char) -> fmt::Result {
62    // With Zfinx, floating-point operands are in the integer registers.
63    let fp_names = if ins.fp_in_x { &INT_NAMES } else { &FP_NAMES };
64    match c {
65        '(' | ',' | ' ' | ')' | '-' => out.write_char(c),
66        'L' => match ins.rs1 {
67            4 => out.write_str("{ra}"),
68            5 => out.write_str("{ra,s0}"),
69            15 => out.write_str("{ra,s0-s11}"),
70            rlist => write!(out, "{{ra,s0-s{}}}", rlist - 5),
71        },
72        '0' => out.write_str(reg(&INT_NAMES, ins.rd)),
73        '1' => out.write_str(reg(&INT_NAMES, ins.rs1)),
74        '2' => out.write_str(reg(&INT_NAMES, ins.rs2)),
75        '3' => out.write_str(reg(fp_names, ins.rd)),
76        '4' => out.write_str(reg(fp_names, ins.rs1)),
77        '5' => out.write_str(reg(fp_names, ins.rs2)),
78        '6' => out.write_str(reg(fp_names, ins.rs3)),
79        '7' => write!(out, "{}", ins.rs1),
80        'i' => write!(out, "{}", ins.imm),
81        'j' => write!(out, "{}", ins.imm2),
82        '>' => write!(out, "{:#x}", ins.imm),
83        'u' => write!(out, "{:#x}", (ins.imm as u32 >> 12) & 0xfffff),
84        'o' => write!(out, "{:#x}", ins.target()),
85        'c' => match csr_name(ins.imm as u16 & 0xfff) {
86            Some(name) => out.write_str(name),
87            None => write!(out, "{:#x}", ins.imm & 0xfff),
88        },
89        'r' if ins.rm == 7 => Ok(()),
90        'R' if ins.rm == 0 => Ok(()),
91        'r' | 'R' => {
92            let names = [
93                "rne", "rtz", "rdn", "rup", "rmm", "unknown", "unknown", "dyn",
94            ];
95            write!(out, ",{}", names[ins.rm as usize & 7])
96        }
97        'P' => write!(out, "{}", ins.imm & !0x1f),
98        'D' => write!(out, "v{}", ins.rd),
99        'A' => write!(out, "v{}", ins.rs1),
100        'B' => write!(out, "v{}", ins.rs2),
101        'M' if ins.masked => out.write_str(",v0.t"),
102        'M' => Ok(()),
103        'Z' => out.write_str("v0"),
104        'T' => write_vtype(out, ins.imm as u32),
105        'F' => out.write_str(FLI_CONSTANTS[ins.rs1 as usize & 31]),
106        'l' => write!(out, "{}", ins.len),
107        'x' => {
108            // As objdump does, padded to a whole number of 16-bit parcels.
109            let parcels = (64 - ins.inst.leading_zeros()).div_ceil(16).max(1) as usize;
110            write!(out, "0x{:01$x}", ins.inst, parcels * 4)
111        }
112        'p' => write_fence_set(out, ins.pred),
113        's' => write_fence_set(out, ins.succ),
114        // Format strings are static, and the tests show every one, so this
115        // is a bug in a table; release builds leave the operand out.
116        _ => {
117            debug_assert!(false, "unknown format character {c:?}");
118            Ok(())
119        }
120    }
121}
122
123/// The name of register `r` (a 5-bit field) in `names`.
124fn reg(names: &'static [&'static str; 32], r: u8) -> &'static str {
125    names[r as usize & 31]
126}
127
128/// Writes a `vtype` immediate as objdump does, such as `e32,m1,ta,ma`, or as
129/// a number if it has reserved values.
130fn write_vtype(out: &mut fmt::Formatter<'_>, vtype: u32) -> fmt::Result {
131    let (sew, lmul) = ((vtype >> 3) & 7, vtype & 7);
132    // LMUL 4 is reserved; its empty name makes the vtype a number below.
133    let lmul = ["m1", "m2", "m4", "m8", "", "mf8", "mf4", "mf2"][lmul as usize];
134    if vtype >> 8 != 0 || sew > 3 || lmul.is_empty() {
135        return write!(out, "{vtype}");
136    }
137    let tail = if vtype & 0x40 != 0 { "ta" } else { "tu" };
138    let mask = if vtype & 0x80 != 0 { "ma" } else { "mu" };
139    write!(out, "e{},{lmul},{tail},{mask}", 8 << sew)
140}
141
142/// Writes the device input, device output, memory read and memory write bits
143/// of a fence predecessor or successor set.
144fn write_fence_set(out: &mut fmt::Formatter<'_>, set: u8) -> fmt::Result {
145    if set == 0 {
146        return out.write_str("unknown");
147    }
148    for (bit, name) in [(8, 'i'), (4, 'o'), (2, 'r'), (1, 'w')] {
149        if set & bit != 0 {
150            out.write_char(name)?;
151        }
152    }
153    Ok(())
154}