superh 0.2.3

Disassembler for the SuperH (SH) instruction set (SH1/2/3/4)
Documentation

superh

superh is a no_std decoder, formatter, and analysis library for the 16-bit SH-1, SH-2, SH-3, and SH-4 instruction sets. Decode behavior is generated from generator/assets/isa.yaml; unknown words and raw data are deliberately not represented as valid instructions.

Decode and display

Decoding is location-independent. Attach an address only when resolving or formatting PC-relative operands.

use superh::{DecodeOptions, DecodeResult, FormatOptions, Ins, Reg, decode};

let result = decode(0x6323, &DecodeOptions::default());
let DecodeResult::Instruction(ins) = result else { panic!("known instruction") };
assert_eq!(ins, Ins::MovRmRn { rn: Reg::R3, rm: Reg::R2 });
assert_eq!(ins.encode(), Some(0x6323));
assert_eq!(ins.at(0x8c01_0000).display(&FormatOptions::default()).to_string(), "mov r2, r3");

Typed instruction values can be encoded directly without a textual assembler. For every valid encoding, decoding and encoding preserves the exact 16-bit word. Encoding returns None if an operand in a manually constructed instruction does not fit that variant's bit field.

Unknown encodings retain the original word:

use superh::{DecodeOptions, DecodeResult, FormatOptions, decode};

let result = decode(0xffff, &DecodeOptions::default());
assert_eq!(result, DecodeResult::Unknown(0xffff));
assert_eq!(result.display_at(0, &FormatOptions::default()).to_string(), ".word 0xffff");

Every valid instruction exposes a stable, non-reused OpcodeId. The ID can be stored by downstream tools and checked with Opcode::from_id. If a consumer already has that opcode, Opcode::decode reconstructs the typed operands without repeating the full opcode search. It still validates the word and selected architecture, returning None if either does not match.

use superh::{DecodeOptions, Opcode};

let instruction = Opcode::MovRmRn
    .decode(0x6323, &DecodeOptions::default())
    .expect("matching encoding");
assert_eq!(instruction.opcode(), Opcode::MovRmRn);

Streaming parser

Parser yields source offset, mapped address, byte size, the original word, and the decode result. Data mode yields a separate Data type.

use superh::{DecodeOptions, ParseEndian, ParseMode, ParsedValue, Parser};

let bytes = [0xe0, 0x01, 0x63, 0x23];
let mut parser = Parser::new(
    &bytes,
    ParseMode::Instruction,
    ParseEndian::Big,
    DecodeOptions::default(),
);
parser.set_address(0x8c01_0000);

for item in parser {
    if let ParsedValue::Instruction { result, .. } = item.value {
        println!("{:08x}: {:?}", item.address, result);
    }
}

Seeking changes only the buffer offset; the mapped address remains base_address + offset, with 32-bit wrapping semantics.

Effects and control flow

Effects distinguish resources that must be accessed from resources that may be accessed under an unknown SH-4 FPSCR mode. They also report memory accesses and control flow without allocating.

use superh::{
    DecodeOptions, DecodeResult, EffectContext, Reg, Resource, decode,
};

let DecodeResult::Instruction(ins) = decode(0x321c, &DecodeOptions::default()) else {
    panic!("known instruction")
};
let effects = ins.effects(EffectContext::default());
assert!(effects.must_read().contains(Resource::Gp(Reg::R1)));
assert!(effects.must_write().contains(Resource::Gp(Reg::R2)));

ins.at(address).branch_target() resolves direct branches, while pc_relative_address() identifies literal-pool references for mov.w, mov.l, and mova.

Structured formatting

FormatIns separates mnemonic and operand rendering and provides typed hooks for registers, immediates, displacements, PC-relative addresses, and branches.

use core::fmt::Write as _;
use superh::{DecodeOptions, DecodeResult, FormatIns, FormatOptions, Reg, decode};

struct Formatter { text: String, options: FormatOptions }
impl core::fmt::Write for Formatter {
    fn write_str(&mut self, value: &str) -> core::fmt::Result {
        self.text.push_str(value);
        Ok(())
    }
}
impl FormatIns for Formatter {
    fn options(&self) -> &FormatOptions { &self.options }
    fn write_reg(&mut self, reg: Reg) -> core::fmt::Result {
        write!(self, "REG({})", reg.number())
    }
}

let DecodeResult::Instruction(ins) = decode(0x6323, &DecodeOptions::default()) else {
    panic!("known instruction")
};
let mut formatter = Formatter { text: String::new(), options: FormatOptions::default() };
formatter.write_ins(&ins, 0).expect("formatting into a string cannot fail");
assert_eq!(formatter.text, "mov REG(2), REG(3)");

Architecture selection

Cargo features are additive and control code size. DecodeOptions::architecture selects one of the architectures compiled into the build.

Feature Compiled instruction sets
sh1 SH-1
sh2 SH-1, SH-2
sh3 SH-1, SH-2, SH-3
sh4 SH-1, SH-2, SH-3, SH-4

The default enables all four. A build with no architecture feature is rejected with a targeted compiler diagnostic.

Development gates

cargo run -p superh-generator
cargo test --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo doc --workspace --all-features --no-deps
cargo run -p superh-fuzz --release -- parse
cargo run -p superh-fuzz --release -- opcode_ids
cargo run -p superh-fuzz --release -- effects

The independent ISA audit is sourced from the Renesas SH-1/SH-2/SH-DSP Software Manual (REJ09B0171), SH-3/SH-3E/SH3-DSP Software Manual Rev. 4.00, and SH-4 Software Manual (REJ09B0318). SH-4A-only encodings are not accepted as SH-4.