Rename crate to powerpc; support extensions

This commit is contained in:
Luke Street 2025-07-06 22:08:58 -06:00
parent 2204612dfb
commit c75d090d82
22 changed files with 9901 additions and 14185 deletions

29
Cargo.lock generated
View File

@ -123,9 +123,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.14.5" version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
@ -135,12 +135,13 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.5.0" version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown",
"serde",
] ]
[[package]] [[package]]
@ -252,33 +253,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]] [[package]]
name = "ppc750cl" name = "powerpc"
version = "0.3.3" version = "0.4.0"
[[package]] [[package]]
name = "ppc750cl-asm" name = "powerpc-asm"
version = "0.3.3" version = "0.4.0"
dependencies = [ dependencies = [
"phf", "phf",
] ]
[[package]] [[package]]
name = "ppc750cl-fuzz" name = "powerpc-fuzz"
version = "0.3.3" version = "0.4.0"
dependencies = [ dependencies = [
"clap", "clap",
"num_cpus", "num_cpus",
"ppc750cl", "powerpc",
] ]
[[package]] [[package]]
name = "ppc750cl-genisa" name = "powerpc-genisa"
version = "0.3.3" version = "0.4.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"indexmap",
"log", "log",
"num-traits", "num-traits",
"phf",
"phf_codegen", "phf_codegen",
"prettyplease", "prettyplease",
"proc-macro2", "proc-macro2",

View File

@ -2,18 +2,11 @@
members = ["asm", "disasm", "fuzz", "genisa"] members = ["asm", "disasm", "fuzz", "genisa"]
resolver = "2" resolver = "2"
[profile.release]
panic = "abort"
[profile.release-lto]
inherits = "release"
lto = true
[workspace.package] [workspace.package]
version = "0.3.3" version = "0.4.0"
edition = "2021" edition = "2021"
authors = ["Luke Street <luke@street.dev>"] authors = ["Luke Street <luke@street.dev>"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
keywords = ["powerpc", "wii", "gamecube", "xbox"] keywords = ["powerpc", "disassembler", "assembler", "ppc"]
repository = "https://github.com/rjkiv/ppc750cl.git" repository = "https://github.com/encounter/powerpc-rs.git"
rust-version = "1.74" rust-version = "1.74"

101
README.md
View File

@ -1,47 +1,96 @@
# ppc750cl [![Build Status]][actions] [![Latest Version]][crates.io] [![Api Rustdoc]][rustdoc] ![Rust Version] # powerpc [![Build Status]][actions] [![Latest Version]][crates.io] [![Api Rustdoc]][rustdoc] ![Rust Version]
[Build Status]: https://github.com/encounter/ppc750cl/actions/workflows/test.yml/badge.svg [Build Status]: https://github.com/encounter/powerpc-rs/actions/workflows/test.yml/badge.svg
[actions]: https://github.com/encounter/ppc750cl/actions [actions]: https://github.com/encounter/powerpc-rs/actions
[Latest Version]: https://img.shields.io/crates/v/ppc750cl.svg [Latest Version]: https://img.shields.io/crates/v/powerpc.svg
[crates.io]: https://crates.io/crates/ppc750cl [crates.io]: https://crates.io/crates/powerpc
[Api Rustdoc]: https://img.shields.io/badge/api-rustdoc-blue.svg [Api Rustdoc]: https://img.shields.io/badge/api-rustdoc-blue.svg
[rustdoc]: https://docs.rs/ppc750cl [rustdoc]: https://docs.rs/powerpc
[Rust Version]: https://img.shields.io/badge/rust-1.74+-blue.svg?maxAge=3600 [Rust Version]: https://img.shields.io/badge/rust-1.74+-blue.svg?maxAge=3600
Rust tools for working with the PowerPC 750CL / 750CXe family of processors. (Previously `ppc750cl`)
### Building Rust disassembler and assembler for the PowerPC ISA.
```shell ### Supported Extensions
cargo run --package ppc750cl-genisa
cargo build --release - PowerPC 64-bit
- Paired Singles
- PowerPC 750CXe "Gekko" (Nintendo GameCube)
- PowerPC 750CL "Broadway" (Nintendo Wii)
- AltiVec
- VMX128
- PowerPC "Xenon" (Xbox 360)
If you need support for other extensions, please open an issue.
## Usage
In Cargo.toml:
```toml
[dependencies]
powerpc = "0.4" # disassembler
powerpc-asm = "0.4" # assembler
``` ```
### Instruction Set Disassembling and printing instructions:
For those unfamiliar with PowerPC, here are some basics. ```rust
- PowerPC 7xx is a family of RISC CPUs produced from 1997 to 2012. use powerpc::{Argument, Extensions, Ins, Opcode, Simm, GPR};
- They operate with 32-bit words and every instruction is 32-bits wide.
- This project focuses (only) on compatibility with the PowerPC 750CL and 750CXe.
- PowerPC 750CL ("Broadway") is used in the Nintendo Wii.
- PowerPC 750CXe ("Gekko") is used in the Nintendo GameCube.
- They add a "paired-singles" SIMD unit and a bunch of other instructions.
### isa.yaml let ins = Ins::new(0x38A00000, Extensions::none());
assert_eq!(ins.op, Opcode::Addi);
The file [isa.yaml](./isa.yaml) contains a full definition of the PowerPC 750CL / 750CXe instruction set. // Basic form
let parsed = ins.basic();
assert_eq!(parsed.args[0], Argument::GPR(GPR(5)));
assert_eq!(parsed.args[1], Argument::GPR(GPR(0)));
assert_eq!(parsed.args[2], Argument::Simm(Simm(0)));
assert_eq!(parsed.to_string(), "addi r5, r0, 0x0");
It powers the disassembler and assembler. // Simplified form
let parsed = ins.simplified();
assert_eq!(parsed.to_string(), "li r5, 0x0");
```
Similarly to LLVM TableGen, the program `ppc750cl-genisa` generates a Rust file implementing an instruction decoder. Assembling instructions:
### Safety & Correctness ```rust
use powerpc_asm::{assemble, Argument, Arguments};
- The disassembler has been fuzzed over all ~4.29 billion possible instructions (via `ppc750cl-fuzz`). let args: Arguments = [
Argument::Unsigned(5),
Argument::Unsigned(0),
Argument::Signed(0),
Argument::None,
Argument::None,
];
let code = assemble("addi", &args).expect("Invalid arguments");
assert_eq!(code, 0x38A00000); // addi r5, r0, 0x0
```
## Building
```
cargo run --package powerpc-genisa
cargo test
```
## isa.yaml
The file [isa.yaml](./isa.yaml) contains a definition of the PowerPC instruction set.
Similarly to LLVM TableGen, the program `powerpc-genisa` generates Rust files implementing core functionality
for the disassembler and assembler.
## Safety & Correctness
- The disassembler has been fuzzed over all ~4.29 billion possible instructions (via `powerpc-fuzz`).
- It is safe to run the disassembler over untrusted byte arrays. - It is safe to run the disassembler over untrusted byte arrays.
- However, no guarantees on correctness are made (yet). Expect bugs. - However, no guarantees on correctness are made (yet). Expect bugs.
### Performance ## Performance
With a single thread on Ryzen 9 3900X: With a single thread on Ryzen 9 3900X:

View File

@ -1,14 +1,14 @@
[package] [package]
name = "ppc750cl-asm" name = "powerpc-asm"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true
description = "Assembler for PowerPC 750CL" description = "PowerPC assembler"
readme = "../README.md" readme = "../README.md"
keywords.workspace = true keywords.workspace = true
repository.workspace = true repository.workspace = true
documentation = "https://docs.rs/ppc750cl-asm" documentation = "https://docs.rs/powerpc-asm"
rust-version.workspace = true rust-version.workspace = true
[features] [features]

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +1,24 @@
use ppc750cl_asm::*; use powerpc_asm::*;
use Argument::{None, Signed as S, Unsigned as U}; use Argument::{None as N, Signed as S, Unsigned as U};
macro_rules! assert_asm { macro_rules! assert_asm {
($mnemonic:literal, $arg1:expr, $arg2:expr, $arg3:expr, $arg4:expr, $arg5: expr, $code:literal) => {{ ($mnemonic:literal, $arg1:expr, $arg2:expr, $arg3:expr, $arg4:expr, $arg5: expr, $code:literal) => {{
assert_eq!(assemble($mnemonic, &[$arg1, $arg2, $arg3, $arg4, $arg5]).unwrap(), $code) assert_eq!(assemble($mnemonic, &[$arg1, $arg2, $arg3, $arg4, $arg5]).unwrap(), $code)
}}; }};
($mnemonic:literal, $arg1:expr, $arg2:expr, $arg3:expr, $arg4:expr, $code:literal) => {{ ($mnemonic:literal, $arg1:expr, $arg2:expr, $arg3:expr, $arg4:expr, $code:literal) => {{
assert_eq!(assemble($mnemonic, &[$arg1, $arg2, $arg3, $arg4, None]).unwrap(), $code) assert_eq!(assemble($mnemonic, &[$arg1, $arg2, $arg3, $arg4, N]).unwrap(), $code)
}}; }};
($mnemonic:literal, $arg1:expr, $arg2:expr, $arg3:expr, $code:literal) => {{ ($mnemonic:literal, $arg1:expr, $arg2:expr, $arg3:expr, $code:literal) => {{
assert_eq!(assemble($mnemonic, &[$arg1, $arg2, $arg3, None, None]).unwrap(), $code) assert_eq!(assemble($mnemonic, &[$arg1, $arg2, $arg3, N, N]).unwrap(), $code)
}}; }};
($mnemonic:literal, $arg1:expr, $arg2:expr, $code:literal) => {{ ($mnemonic:literal, $arg1:expr, $arg2:expr, $code:literal) => {{
assert_eq!(assemble($mnemonic, &[$arg1, $arg2, None, None, None]).unwrap(), $code) assert_eq!(assemble($mnemonic, &[$arg1, $arg2, N, N, N]).unwrap(), $code)
}}; }};
($mnemonic:literal, $arg1:expr, $code:literal) => {{ ($mnemonic:literal, $arg1:expr, $code:literal) => {{
assert_eq!(assemble($mnemonic, &[$arg1, None, None, None, None]).unwrap(), $code) assert_eq!(assemble($mnemonic, &[$arg1, N, N, N, N]).unwrap(), $code)
}}; }};
($mnemonic:literal, $code:literal) => {{ ($mnemonic:literal, $code:literal) => {{
assert_eq!(assemble($mnemonic, &[None, None, None, None, None]).unwrap(), $code) assert_eq!(assemble($mnemonic, &[N, N, N, N, N]).unwrap(), $code)
}}; }};
} }

View File

@ -1,14 +1,14 @@
[package] [package]
name = "ppc750cl" name = "powerpc"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true
description = "Disassembler for PowerPC 750CL" description = "PowerPC disassembler"
readme = "../README.md" readme = "../README.md"
keywords.workspace = true keywords.workspace = true
repository.workspace = true repository.workspace = true
documentation = "https://docs.rs/ppc750cl" documentation = "https://docs.rs/powerpc"
rust-version.workspace = true rust-version.workspace = true
[dependencies] [dependencies]

View File

@ -1,13 +1,13 @@
use core::{
fmt,
fmt::{Display, Formatter, LowerHex},
};
use crate::generated::{ use crate::generated::{
parse_basic, parse_defs, parse_simplified, parse_uses, Arguments, Opcode, EMPTY_ARGS, parse_basic, parse_defs, parse_simplified, parse_uses, Arguments, Extension, Opcode, EMPTY_ARGS,
};
use core::{
fmt::{self, Display, Formatter, LowerHex},
hash::{Hash, Hasher},
ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not},
}; };
/// A PowerPC 750CL instruction. /// A PowerPC instruction.
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)] #[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
pub struct Ins { pub struct Ins {
pub code: u32, pub code: u32,
@ -16,8 +16,9 @@ pub struct Ins {
impl Ins { impl Ins {
/// Create a new instruction from its raw code. /// Create a new instruction from its raw code.
pub fn new(code: u32) -> Self { #[inline]
Self { code, op: Opcode::detect(code) } pub fn new(code: u32, extensions: Extensions) -> Self {
Self { code, op: Opcode::detect(code, extensions) }
} }
/// Parse the instruction into a simplified mnemonic, if any match. /// Parse the instruction into a simplified mnemonic, if any match.
@ -129,6 +130,22 @@ impl Ins {
} }
} }
impl Hash for Ins {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.code.hash(state);
}
}
impl Hash for Opcode {
/// Opcode enum discriminants are not stable.
/// Instead, hash the mnemonic string.
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.mnemonic().hash(state);
}
}
macro_rules! field_arg_no_display { macro_rules! field_arg_no_display {
($name:ident, $typ:ident) => { ($name:ident, $typ:ident) => {
#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
@ -336,7 +353,7 @@ impl Display for Argument {
} }
} }
/// A parsed PowerPC 750CL instruction. /// A parsed PowerPC instruction.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParsedIns { pub struct ParsedIns {
pub mnemonic: &'static str, pub mnemonic: &'static str,
@ -344,6 +361,7 @@ pub struct ParsedIns {
} }
impl Default for ParsedIns { impl Default for ParsedIns {
#[inline]
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
@ -351,6 +369,7 @@ impl Default for ParsedIns {
impl ParsedIns { impl ParsedIns {
/// An empty parsed instruction. /// An empty parsed instruction.
#[inline]
pub const fn new() -> Self { pub const fn new() -> Self {
Self { mnemonic: "<illegal>", args: EMPTY_ARGS } Self { mnemonic: "<illegal>", args: EMPTY_ARGS }
} }
@ -412,21 +431,30 @@ impl LowerHex for SignedHexLiteral<i32> {
pub struct InsIter<'a> { pub struct InsIter<'a> {
address: u32, address: u32,
extensions: Extensions,
data: &'a [u8], data: &'a [u8],
} }
impl<'a> InsIter<'a> { impl<'a> InsIter<'a> {
pub fn new(data: &'a [u8], address: u32) -> Self { #[inline]
Self { address, data } pub fn new(data: &'a [u8], address: u32, extensions: Extensions) -> Self {
Self { address, extensions, data }
} }
#[inline]
pub fn address(&self) -> u32 { pub fn address(&self) -> u32 {
self.address self.address
} }
#[inline]
pub fn data(&self) -> &'a [u8] { pub fn data(&self) -> &'a [u8] {
self.data self.data
} }
#[inline]
pub fn extensions(&self) -> Extensions {
self.extensions
}
} }
impl Iterator for InsIter<'_> { impl Iterator for InsIter<'_> {
@ -439,10 +467,194 @@ impl Iterator for InsIter<'_> {
// SAFETY: The slice is guaranteed to be at least 4 bytes long. // SAFETY: The slice is guaranteed to be at least 4 bytes long.
let chunk = unsafe { *(self.data.as_ptr() as *const [u8; 4]) }; let chunk = unsafe { *(self.data.as_ptr() as *const [u8; 4]) };
let ins = Ins::new(u32::from_be_bytes(chunk)); let ins = Ins::new(u32::from_be_bytes(chunk), self.extensions);
let addr = self.address; let addr = self.address;
self.address += 4; self.address += 4;
self.data = &self.data[4..]; self.data = &self.data[4..];
Some((addr, ins)) Some((addr, ins))
} }
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Extensions(u32);
impl Extensions {
/// Creates an empty set of extensions.
#[inline]
pub const fn none() -> Self {
Self(0)
}
/// The set of extensions used by the PowerPC 750CXe (Gekko) / 750CL (Broadway) CPUs
/// used in the GameCube and Wii respectively.
#[inline]
pub const fn gekko_broadway() -> Self {
Self::from_bitmask(Extension::PairedSingles.bitmask())
}
/// The set of extensions used by the PowerPC Xenon CPU used in the Xbox 360.
#[inline]
pub const fn xenon() -> Self {
Self::from_bitmask(
Extension::Ppc64.bitmask() | Extension::AltiVec.bitmask() | Extension::Vmx128.bitmask(),
)
}
/// Checks if the given extension (and all required extensions) are enabled.
#[inline]
pub const fn contains(&self, ext: Extension) -> bool {
let bitmask = ext.bitmask();
(self.0 & bitmask) == bitmask
}
/// Checks if the given set of extensions are enabled.
#[inline]
pub const fn contains_all(&self, other: Extensions) -> bool {
(self.0 & other.0) == other.0
}
/// Enables the given extension. Implicitly enables all required extensions.
#[inline]
pub const fn insert(&mut self, ext: Extension) {
self.0 |= ext.bitmask();
}
/// Disables the given extension.
#[inline]
pub const fn remove(&mut self, ext: Extension) {
// Instead of using bitmask, which includes required extensions,
// we only clear the bit for the specific extension.
self.0 &= !(1 << (ext as u32));
}
/// Enables or disables the given extension.
#[inline]
pub const fn set(&mut self, ext: Extension, value: bool) {
if value {
self.insert(ext);
} else {
self.remove(ext);
}
}
/// Returns whether the extensions set is empty.
#[inline]
pub const fn is_empty(&self) -> bool {
self.0 == 0
}
/// Returns the raw bitmask of the extensions.
#[inline]
pub const fn bitmask(&self) -> u32 {
self.0
}
/// Creates a set of extensions from a raw bitmask.
#[inline]
pub const fn from_bitmask(bitmask: u32) -> Self {
Self(bitmask)
}
/// Creates a set of extensions from a single extension (and its required extensions).
#[inline]
pub const fn from_extension(ext: Extension) -> Self {
Self(ext.bitmask())
}
}
impl Default for Extensions {
#[inline]
fn default() -> Self {
Self::none()
}
}
impl From<Extension> for Extensions {
#[inline]
fn from(ext: Extension) -> Self {
Self::from_extension(ext)
}
}
impl BitOr for Extensions {
type Output = Extensions;
#[inline]
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0)
}
}
impl BitOr<Extension> for Extensions {
type Output = Extensions;
#[inline]
fn bitor(self, rhs: Extension) -> Self::Output {
Self(self.0 | rhs.bitmask())
}
}
impl BitOrAssign<Extension> for Extensions {
#[inline]
fn bitor_assign(&mut self, rhs: Extension) {
self.0 |= rhs.bitmask();
}
}
impl BitAnd for Extensions {
type Output = Extensions;
#[inline]
fn bitand(self, rhs: Self) -> Self::Output {
Self(self.0 & rhs.0)
}
}
impl BitAnd<Extension> for Extensions {
type Output = Extensions;
#[inline]
fn bitand(self, rhs: Extension) -> Self::Output {
Self(self.0 & rhs.bitmask())
}
}
impl BitAndAssign<Extension> for Extensions {
#[inline]
fn bitand_assign(&mut self, rhs: Extension) {
self.0 &= rhs.bitmask();
}
}
impl Not for Extensions {
type Output = Extensions;
#[inline]
fn not(self) -> Self::Output {
Self(!self.0)
}
}
impl BitOr for Extension {
type Output = Extensions;
#[inline]
fn bitor(self, rhs: Self) -> Self::Output {
Extensions(self.bitmask() | rhs.bitmask())
}
}
impl Hash for Extension {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.bitmask().hash(state);
}
}
impl Display for Extension {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@ mod disasm;
mod generated; mod generated;
pub use disasm::{ pub use disasm::{
Argument, BranchDest, CRBit, CRField, Ins, InsIter, Offset, OpaqueU, ParsedIns, Simm, Uimm, Argument, BranchDest, CRBit, CRField, Extensions, Ins, InsIter, Offset, OpaqueU, ParsedIns,
FPR, GPR, GQR, SPR, SR, Simm, Uimm, FPR, GPR, GQR, SPR, SR,
}; };
pub use generated::{Arguments, Opcode}; pub use generated::{Arguments, Extension, Opcode};

View File

@ -1,11 +1,13 @@
use ppc750cl::Ins; use powerpc::{Extension, Extensions, Ins};
const EXTENSIONS: Extensions = Extensions::from_extension(Extension::AltiVec);
macro_rules! assert_asm { macro_rules! assert_asm {
($ins:ident, $disasm:literal) => {{ ($ins:ident, $disasm:literal) => {{
assert_eq!(format!("{}", $ins.simplified()), $disasm) assert_eq!(format!("{}", $ins.simplified()), $disasm)
}}; }};
($code:literal, $disasm:literal) => {{ ($code:literal, $disasm:literal) => {{
let ins = Ins::new($code); let ins = Ins::new($code, EXTENSIONS);
assert_eq!(format!("{}", ins.simplified()), $disasm) assert_eq!(format!("{}", ins.simplified()), $disasm)
}}; }};
} }

View File

@ -1,11 +1,13 @@
use ppc750cl::{Argument, Ins, InsIter, Opcode, GPR}; use powerpc::{Argument, Extensions, Ins, InsIter, Opcode, GPR};
const EXTENSIONS: Extensions = Extensions::none();
macro_rules! assert_asm { macro_rules! assert_asm {
($ins:ident, $disasm:literal) => {{ ($ins:ident, $disasm:literal) => {{
assert_eq!(format!("{}", $ins.simplified()), $disasm) assert_eq!(format!("{}", $ins.simplified()), $disasm)
}}; }};
($code:literal, $disasm:literal) => {{ ($code:literal, $disasm:literal) => {{
let ins = Ins::new($code); let ins = Ins::new($code, EXTENSIONS);
assert_eq!(format!("{}", ins.simplified()), $disasm) assert_eq!(format!("{}", ins.simplified()), $disasm)
}}; }};
} }
@ -15,7 +17,7 @@ macro_rules! assert_basic {
assert_eq!(format!("{}", $ins.basic_form()), $disasm) assert_eq!(format!("{}", $ins.basic_form()), $disasm)
}}; }};
($code:literal, $disasm:literal) => {{ ($code:literal, $disasm:literal) => {{
let ins = Ins::new($code); let ins = Ins::new($code, EXTENSIONS);
assert_eq!(format!("{}", ins.basic()), $disasm) assert_eq!(format!("{}", ins.basic()), $disasm)
}}; }};
} }
@ -30,7 +32,7 @@ fn test_ins_add() {
#[test] #[test]
fn test_ins_addc() { fn test_ins_addc() {
let ins = Ins::new(0x7c002014); let ins = Ins::new(0x7c002014, EXTENSIONS);
assert_eq!(ins.op, Opcode::Addc); assert_eq!(ins.op, Opcode::Addc);
// assert_eq!(ins.fields(), vec![rD(GPR(0)), rA(GPR(0)), rB(GPR(4))]); // assert_eq!(ins.fields(), vec![rD(GPR(0)), rA(GPR(0)), rB(GPR(4))]);
assert_asm!(ins, "addc r0, r0, r4"); assert_asm!(ins, "addc r0, r0, r4");
@ -42,7 +44,7 @@ fn test_ins_addc() {
#[test] #[test]
fn test_ins_addi() { fn test_ins_addi() {
let ins = Ins::new(0x38010140); let ins = Ins::new(0x38010140, EXTENSIONS);
assert_eq!(ins.op, Opcode::Addi); assert_eq!(ins.op, Opcode::Addi);
// assert_eq!( // assert_eq!(
// ins.fields(), // ins.fields(),
@ -214,11 +216,6 @@ fn test_ins_cmpli() {
assert_asm!(0x2884F8F0, "cmplwi cr1, r4, 0xf8f0"); assert_asm!(0x2884F8F0, "cmplwi cr1, r4, 0xf8f0");
} }
#[test]
fn test_ins_cntlzd() {
assert_asm!(0x7CA30074, "cntlzd r3, r5");
}
#[test] #[test]
fn test_ins_cntlzw() { fn test_ins_cntlzw() {
assert_asm!(0x7C030034, "cntlzw r3, r0"); assert_asm!(0x7C030034, "cntlzw r3, r0");
@ -291,21 +288,6 @@ fn test_ins_dcbz() {
assert_asm!(0x7C001FEC, "dcbz r0, r3"); assert_asm!(0x7C001FEC, "dcbz r0, r3");
} }
#[test]
fn test_ins_dcbz_l() {
assert_asm!(0x10061FEC, "dcbz_l r6, r3");
}
#[test]
fn test_ins_divd() {
assert_asm!(0x7CA63BD2, "divd r5, r6, r7");
}
#[test]
fn test_ins_divdu() {
assert_asm!(0x7C839392, "divdu r4, r3, r18");
}
#[test] #[test]
fn test_ins_divw() { fn test_ins_divw() {
assert_asm!(0x7C8073D6, "divw r4, r0, r14"); assert_asm!(0x7C8073D6, "divw r4, r0, r14");
@ -328,12 +310,6 @@ fn test_ins_extsh() {
assert_asm!(0x7C000735, "extsh. r0, r0"); assert_asm!(0x7C000735, "extsh. r0, r0");
} }
#[test]
fn test_ins_extsw() {
assert_asm!(0x7CC307B4, "extsw r3, r6");
assert_asm!(0x7CC307B5, "extsw. r3, r6");
}
#[test] #[test]
fn test_ins_fabs() { fn test_ins_fabs() {
assert_asm!(0xFC000A10, "fabs f0, f1"); assert_asm!(0xFC000A10, "fabs f0, f1");
@ -349,11 +325,6 @@ fn test_ins_fadds() {
assert_asm!(0xEC41602A, "fadds f2, f1, f12"); assert_asm!(0xEC41602A, "fadds f2, f1, f12");
} }
#[test]
fn test_ins_fcfid() {
assert_asm!(0xFC602E9C, "fcfid f3, f5");
}
#[test] #[test]
fn test_ins_fcmpo() { fn test_ins_fcmpo() {
assert_asm!(0xFC00C840, "fcmpo cr0, f0, f25"); assert_asm!(0xFC00C840, "fcmpo cr0, f0, f25");
@ -364,16 +335,6 @@ fn test_ins_fcmpu() {
assert_asm!(0xFC00D000, "fcmpu cr0, f0, f26"); assert_asm!(0xFC00D000, "fcmpu cr0, f0, f26");
} }
#[test]
fn test_ins_fctid() {
assert_asm!(0xFC60065C, "fctid f3, f0");
}
#[test]
fn test_ins_fctidz() {
assert_asm!(0xFC60065E, "fctidz f3, f0");
}
#[test] #[test]
fn test_ins_fctiwz() { fn test_ins_fctiwz() {
assert_asm!(0xFC20001E, "fctiwz f1, f0"); assert_asm!(0xFC20001E, "fctiwz f1, f0");
@ -492,33 +453,6 @@ fn test_ins_lbzx() {
assert_asm!(0x7C0300AE, "lbzx r0, r3, r0"); assert_asm!(0x7C0300AE, "lbzx r0, r3, r0");
} }
#[test]
fn test_ins_ld() {
assert_asm!(0xebe10058, "ld r31, 0x58(r1)");
assert_asm!(0xe9790010, "ld r11, 0x10(r25)");
}
#[test]
fn test_ins_ldarx() {
assert_asm!(0x7C6538A8, "ldarx r3, r5, r7");
}
#[test]
fn test_ins_ldu() {
assert_asm!(0xe97cfff9, "ldu r11, -0x8(r28)");
assert_asm!(0xe8deffe9, "ldu r6, -0x18(r30)");
}
#[test]
fn test_ins_ldux() {
assert_asm!(0x7C60286A, "ldux r3, r0, r5");
}
#[test]
fn test_ins_ldx() {
assert_asm!(0x7C60282A, "ldx r3, r0, r5");
}
#[test] #[test]
fn test_ins_lfd() { fn test_ins_lfd() {
assert_asm!(0xC80140C8, "lfd f0, 0x40c8(r1)"); assert_asm!(0xC80140C8, "lfd f0, 0x40c8(r1)");
@ -600,21 +534,6 @@ fn test_ins_lmw() {
assert_asm!(0xBB210444, "lmw r25, 0x444(r1)"); assert_asm!(0xBB210444, "lmw r25, 0x444(r1)");
} }
#[test]
fn test_ins_lwa() {
assert_asm!(0xe97fffea, "lwa r11, -0x18(r31)");
}
#[test]
fn test_ins_lwaux() {
assert_asm!(0x7C8532EA, "lwaux r4, r5, r6");
}
#[test]
fn test_ins_lwax() {
assert_asm!(0x7CA63AAA, "lwax r5, r6, r7");
}
#[test] #[test]
fn test_ins_lwbrx() { fn test_ins_lwbrx() {
assert_asm!(0x7D80242C, "lwbrx r12, r0, r4"); assert_asm!(0x7D80242C, "lwbrx r12, r0, r4");
@ -716,12 +635,6 @@ fn test_ins_mtmsr() {
assert_asm!(0x7C000124, "mtmsr r0"); assert_asm!(0x7C000124, "mtmsr r0");
} }
#[test]
fn test_ins_mtmsrd() {
assert_asm!(0x7C000164, "mtmsrd r0, 0");
assert_asm!(0x7D210164, "mtmsrd r9, 1");
}
#[test] #[test]
fn test_ins_mtspr() { fn test_ins_mtspr() {
assert_asm!(0x7E75FBA6, "mtspr DABR, r19"); assert_asm!(0x7E75FBA6, "mtspr DABR, r19");
@ -741,26 +654,6 @@ fn test_ins_mtsr() {
assert_asm!(0x7E0001A4, "mtsr 0, r16"); assert_asm!(0x7E0001A4, "mtsr 0, r16");
} }
#[test]
fn test_ins_mtsrd() {
assert_asm!(0x7E0000A4, "mtsrd 0, r16");
}
#[test]
fn test_ins_mtsrdin() {
assert_asm!(0x7C8040E4, "mtsrdin r4, r8");
}
#[test]
fn test_ins_mulhd() {
assert_asm!(0x7C7CF892, "mulhd r3, r28, r31");
}
#[test]
fn test_ins_mulhdu() {
assert_asm!(0x7CBCF812, "mulhdu r5, r28, r31");
}
#[test] #[test]
fn test_ins_mulhw() { fn test_ins_mulhw() {
assert_asm!(0x7C7F2096, "mulhw r3, r31, r4"); assert_asm!(0x7C7F2096, "mulhw r3, r31, r4");
@ -771,12 +664,6 @@ fn test_ins_mulhwu() {
assert_asm!(0x7C7D0016, "mulhwu r3, r29, r0"); assert_asm!(0x7C7D0016, "mulhwu r3, r29, r0");
} }
#[test]
fn test_ins_mulld() {
assert_asm!(0x7C6419D2, "mulld r3, r4, r3");
assert_asm!(0x7d6b49d2, "mulld r11, r11, r9");
}
#[test] #[test]
fn test_ins_mulli() { fn test_ins_mulli() {
assert_asm!(0x1C001880, "mulli r0, r0, 0x1880"); assert_asm!(0x1C001880, "mulli r0, r0, 0x1880");
@ -826,230 +713,11 @@ fn test_ins_oris() {
assert_asm!(0x67A06800, "oris r0, r29, 0x6800"); assert_asm!(0x67A06800, "oris r0, r29, 0x6800");
} }
// #[test]
// fn test_ins_psq_l() {
// assert_asm!(0xE02500AC, "psq_l f1, 0xac(r5), 0, qr0");
// }
// #[test]
// fn test_ins_psq_lu() {
// assert_asm!(0xE5435010, "psq_lu f10, 0x10(r3), 0, qr5");
// }
// #[test]
// fn test_ins_psq_lx() {
// let ins = Ins::new(0x1000000C);
// assert_eq!(ins.op, Opcode::PsqLx);
// // assert_eq!(
// // ins.fields(),
// // vec![
// // frD(FPR(0)),
// // rA(GPR(0)),
// // rB(GPR(0)),
// // ps_WX(OpaqueU(0)),
// // ps_IX(GQR(0)),
// // ]
// // );
// assert_eq!(
// ins.defs(),
// [Argument::FPR(FPR(0)), Argument::None, Argument::None, Argument::None, Argument::None]
// );
// assert_eq!(
// ins.uses(),
// [Argument::None, Argument::GPR(GPR(0)), Argument::None, Argument::None, Argument::None]
// );
// assert_asm!(0x1000000C, "psq_lx f0, r0, r0, 0, qr0");
// }
// #[test]
// fn test_ins_psq_st() {
// assert_asm!(0xF1230210, "psq_st f9, 0x210(r3), 0, qr0");
// assert_asm!(0xF1238008, "psq_st f9, 0x8(r3), 1, qr0");
// }
// #[test]
// fn test_ins_psq_stu() {
// assert_asm!(0xF40A0020, "psq_stu f0, 0x20(r10), 0, qr0");
// }
// #[test]
// fn test_ins_psq_stx() {
// assert_asm!(0x13E1000E, "psq_stx f31, r1, r0, 0, qr0");
// }
// #[test]
// fn test_ins_ps_abs() {
// assert_asm!(0x10A03210, "ps_abs f5, f6");
// }
// #[test]
// fn test_ins_ps_add() {
// assert_asm!(0x1006382A, "ps_add f0, f6, f7");
// }
// #[test]
// fn test_ins_ps_cmpo0() {
// assert_asm!(0x10070840, "ps_cmpo0 cr0, f7, f1");
// }
// #[test]
// fn test_ins_ps_cmpu0() {
// assert_asm!(0x10003000, "ps_cmpu0 cr0, f0, f6");
// }
// #[test]
// fn test_ins_ps_cmpu1() {
// assert_asm!(0x10003080, "ps_cmpu1 cr0, f0, f6");
// }
// #[test]
// fn test_ins_ps_madd() {
// assert_asm!(0x112141FA, "ps_madd f9, f1, f7, f8");
// }
// #[test]
// fn test_ins_ps_madds0() {
// assert_asm!(0x10AC299C, "ps_madds0 f5, f12, f6, f5");
// }
// #[test]
// fn test_ins_ps_madds1() {
// assert_asm!(0x110640DE, "ps_madds1 f8, f6, f3, f8");
// }
// #[test]
// fn test_ins_ps_merge00() {
// assert_asm!(0x10400420, "ps_merge00 f2, f0, f0");
// }
// #[test]
// fn test_ins_ps_merge01() {
// assert_asm!(0x10400C60, "ps_merge01 f2, f0, f1");
// }
// #[test]
// fn test_ins_ps_merge10() {
// assert_asm!(0x104004A0, "ps_merge10 f2, f0, f0");
// }
// #[test]
// fn test_ins_ps_merge11() {
// assert_asm!(0x10AA14E0, "ps_merge11 f5, f10, f2");
// }
// #[test]
// fn test_ins_ps_mr() {
// assert_asm!(0x10200090, "ps_mr f1, f0");
// }
// #[test]
// fn test_ins_ps_msub() {
// assert_asm!(0x10A53778, "ps_msub f5, f5, f29, f6");
// }
// #[test]
// fn test_ins_ps_mul() {
// assert_asm!(0x10000032, "ps_mul f0, f0, f0");
// }
// #[test]
// fn test_ins_ps_muls0() {
// assert_asm!(0x100002D8, "ps_muls0 f0, f0, f11");
// }
// #[test]
// fn test_ins_ps_muls1() {
// assert_asm!(0x10A2005A, "ps_muls1 f5, f2, f1");
// }
// #[test]
// fn test_ins_ps_nabs() {
// assert_asm!(0x10803210, "ps_abs f4, f6");
// }
// #[test]
// fn test_ins_ps_neg() {
// assert_asm!(0x10E03850, "ps_neg f7, f7");
// }
// #[test]
// fn test_ins_ps_nmadd() {
// assert_asm!(0x10CB30FE, "ps_nmadd f6, f11, f3, f6");
// }
// #[test]
// fn test_ins_ps_nmsub() {
// assert_asm!(0x107E083C, "ps_nmsub f3, f30, f0, f1");
// }
// #[test]
// fn test_ins_ps_sel() {
// assert_asm!(0x106428EE, "ps_sel f3, f4, f3, f5");
// }
// #[test]
// fn test_ins_ps_sub() {
// assert_asm!(0x10A92828, "ps_sub f5, f9, f5");
// }
// #[test]
// fn test_ins_ps_sum0() {
// assert_asm!(0x10230854, "ps_sum0 f1, f3, f1, f1");
// }
// #[test]
// fn test_ins_ps_sum1() {
// assert_asm!(0x10A12956, "ps_sum1 f5, f1, f5, f5");
// }
#[test] #[test]
fn test_ins_rfi() { fn test_ins_rfi() {
assert_asm!(0x4C000064, "rfi"); assert_asm!(0x4C000064, "rfi");
} }
#[test]
fn test_ins_rfid() {
assert_asm!(0x4c000024, "rfid");
}
#[test]
fn test_ins_rldcl() {
assert_asm!(0x780336D0, "rldcl r3, r0, r6, 27");
assert_asm!(0x78033010, "rotld r3, r0, r6");
}
#[test]
fn test_ins_rldcr() {
assert_asm!(0x78A345D2, "rldcr r3, r5, r8, 23");
}
#[test]
fn test_ins_rldic() {
assert_asm!(0x78C51928, "rldic r5, r6, 3, 36");
}
#[test]
fn test_ins_rldicl() {
assert_asm!(0x78c50020, "rldicl r5, r6, 0, 32");
assert_asm!(0x7bab07a0, "rldicl r11, r29, 0, 62");
}
#[test]
fn test_ins_rldicr() {
assert_asm!(0x7883ffe6, "rldicr r3, r4, 63, 63");
assert_asm!(0x798c37e4, "rldicr r12, r12, 6, 63");
assert_asm!(0x798c07c6, "rldicr r12, r12, 32, 31");
assert_asm!(0x798ccfe6, "rldicr r12, r12, 57, 63");
}
#[test]
fn test_ins_rldimi() {
assert_asm!(0x78a3a04e, "rldimi r3, r5, 52, 1");
assert_asm!(0x794b000e, "rldimi r11, r10, 32, 0");
assert_asm!(0x780331CC, "rldimi r3, r0, 6, 7");
}
#[test] #[test]
fn test_ins_rlwimi() { fn test_ins_rlwimi() {
assert_asm!(0x500306FE, "rlwimi r3, r0, 0, 27, 31"); assert_asm!(0x500306FE, "rlwimi r3, r0, 0, 27, 31");
@ -1090,39 +758,11 @@ fn test_ins_sc() {
assert_asm!(0x44000002, "sc"); assert_asm!(0x44000002, "sc");
} }
#[test]
fn test_ins_slbia() {
assert_asm!(0x7c0003e4, "slbia");
}
#[test]
fn test_ins_slbie() {
assert_asm!(0x7C002B64, "slbie r5");
}
#[test]
fn test_ins_sld() {
assert_asm!(0x7d6a5036, "sld r10, r11, r10");
assert_asm!(0x7D034836, "sld r3, r8, r9");
}
#[test] #[test]
fn test_ins_slw() { fn test_ins_slw() {
assert_asm!(0x7C042830, "slw r4, r0, r5"); assert_asm!(0x7C042830, "slw r4, r0, r5");
} }
#[test]
fn test_ins_srad() {
assert_asm!(0x7d0b5e34, "srad r11, r8, r11");
assert_asm!(0x7C033634, "srad r3, r0, r6");
}
#[test]
fn test_ins_sradi() {
assert_asm!(0x7cc4a674, "sradi r4, r6, 20");
assert_asm!(0x7d6b0676, "sradi r11, r11, 32");
}
#[test] #[test]
fn test_ins_sraw() { fn test_ins_sraw() {
assert_asm!(0x7C043E30, "sraw r4, r0, r7"); assert_asm!(0x7C043E30, "sraw r4, r0, r7");
@ -1134,14 +774,6 @@ fn test_ins_srawi() {
assert_asm!(0x7C001670, "srawi r0, r0, 2"); assert_asm!(0x7C001670, "srawi r0, r0, 2");
} }
#[test]
fn test_ins_srd() {
assert_asm!(0x7d0a4c36, "srd r10, r8, r9");
assert_asm!(0x7d675436, "srd r7, r11, r10");
assert_asm!(0x7C001C36, "srd r0, r0, r3");
assert_asm!(0x7C600436, "srd r0, r3, r0");
}
#[test] #[test]
fn test_ins_srw() { fn test_ins_srw() {
assert_asm!(0x7C001C30, "srw r0, r0, r3"); assert_asm!(0x7C001C30, "srw r0, r0, r3");
@ -1170,35 +802,6 @@ fn test_ins_stbx() {
assert_asm!(0x7C03F9AE, "stbx r0, r3, r31"); assert_asm!(0x7C03F9AE, "stbx r0, r3, r31");
} }
#[test]
fn test_ins_std() {
assert_asm!(0xfbe1fff0, "std r31, -0x10(r1)");
}
#[test]
fn test_ins_stdcx() {
assert_asm!(0x7CA749AD, "stdcx. r5, r7, r9");
assert_asm!(0x7fc0e9ad, "stdcx. r30, r0, r29");
}
#[test]
fn test_ins_stdu() {
assert_asm!(0xf9690009, "stdu r11, 0x8(r9)");
assert_asm!(0xf97ffff9, "stdu r11, -0x8(r31)");
}
#[test]
fn test_ins_stdux() {
assert_asm!(0x7C03316A, "stdux r0, r3, r6");
assert_asm!(0x7d5cc96a, "stdux r10, r28, r25");
}
#[test]
fn test_ins_stdx() {
assert_asm!(0x7CA7F92A, "stdx r5, r7, r31");
assert_asm!(0x7cc3212a, "stdx r6, r3, r4");
}
#[test] #[test]
fn test_ins_stfd() { fn test_ins_stfd() {
assert_asm!(0xD80D97B0, "stfd f0, -0x6850(r13)"); assert_asm!(0xD80D97B0, "stfd f0, -0x6850(r13)");
@ -1319,16 +922,6 @@ fn test_ins_sync() {
assert_asm!(0x7c4004Ac, "ptesync"); assert_asm!(0x7c4004Ac, "ptesync");
} }
#[test]
fn test_ins_td() {
assert_asm!(0x7DC30088, "td 14, r3, r0");
}
#[test]
fn test_ins_tdi() {
assert_asm!(0x09830058, "tdi 12, r3, 0x58");
}
#[test] #[test]
fn test_tlbie() { fn test_tlbie() {
assert_asm!(0x7C001A64, "tlbie r3"); assert_asm!(0x7C001A64, "tlbie r3");
@ -1373,8 +966,9 @@ fn test_ins_xoris() {
#[test] #[test]
fn test_ins_iter() { fn test_ins_iter() {
let mut iter = InsIter::new(&[0x7C, 0x43, 0x22, 0x14, 0x7E, 0x1A, 0x02, 0xA6, 0xFF], 0); let mut iter =
assert_eq!(iter.next(), Some((0, Ins::new(0x7C432214)))); InsIter::new(&[0x7C, 0x43, 0x22, 0x14, 0x7E, 0x1A, 0x02, 0xA6, 0xFF], 0, EXTENSIONS);
assert_eq!(iter.next(), Some((4, Ins::new(0x7E1A02A6)))); assert_eq!(iter.next(), Some((0, Ins::new(0x7C432214, EXTENSIONS))));
assert_eq!(iter.next(), Some((4, Ins::new(0x7E1A02A6, EXTENSIONS))));
assert_eq!(iter.next(), None); assert_eq!(iter.next(), None);
} }

245
disasm/tests/test_ppc64.rs Normal file
View File

@ -0,0 +1,245 @@
use powerpc::{Extension, Extensions, Ins};
const EXTENSIONS: Extensions = Extensions::from_extension(Extension::Ppc64);
macro_rules! assert_asm {
($ins:ident, $disasm:literal) => {{
assert_eq!(format!("{}", $ins.simplified()), $disasm)
}};
($code:literal, $disasm:literal) => {{
let ins = Ins::new($code, EXTENSIONS);
assert_eq!(format!("{}", ins.simplified()), $disasm)
}};
}
#[test]
fn test_ins_cntlzd() {
assert_asm!(0x7CA30074, "cntlzd r3, r5");
}
#[test]
fn test_ins_divd() {
assert_asm!(0x7CA63BD2, "divd r5, r6, r7");
}
#[test]
fn test_ins_divdu() {
assert_asm!(0x7C839392, "divdu r4, r3, r18");
}
#[test]
fn test_ins_extsw() {
assert_asm!(0x7CC307B4, "extsw r3, r6");
assert_asm!(0x7CC307B5, "extsw. r3, r6");
}
#[test]
fn test_ins_fcfid() {
assert_asm!(0xFC602E9C, "fcfid f3, f5");
}
#[test]
fn test_ins_fctid() {
assert_asm!(0xFC60065C, "fctid f3, f0");
}
#[test]
fn test_ins_fctidz() {
assert_asm!(0xFC60065E, "fctidz f3, f0");
}
#[test]
fn test_ins_ld() {
assert_asm!(0xebe10058, "ld r31, 0x58(r1)");
assert_asm!(0xe9790010, "ld r11, 0x10(r25)");
}
#[test]
fn test_ins_ldarx() {
assert_asm!(0x7C6538A8, "ldarx r3, r5, r7");
}
#[test]
fn test_ins_ldu() {
assert_asm!(0xe97cfff9, "ldu r11, -0x8(r28)");
assert_asm!(0xe8deffe9, "ldu r6, -0x18(r30)");
}
#[test]
fn test_ins_ldux() {
assert_asm!(0x7C60286A, "ldux r3, r0, r5");
}
#[test]
fn test_ins_ldx() {
assert_asm!(0x7C60282A, "ldx r3, r0, r5");
}
#[test]
fn test_ins_lwa() {
assert_asm!(0xe97fffea, "lwa r11, -0x18(r31)");
}
#[test]
fn test_ins_lwaux() {
assert_asm!(0x7C8532EA, "lwaux r4, r5, r6");
}
#[test]
fn test_ins_lwax() {
assert_asm!(0x7CA63AAA, "lwax r5, r6, r7");
}
#[test]
fn test_ins_mtmsrd() {
assert_asm!(0x7C000164, "mtmsrd r0, 0");
assert_asm!(0x7D210164, "mtmsrd r9, 1");
}
#[test]
fn test_ins_mtsrd() {
assert_asm!(0x7E0000A4, "mtsrd 0, r16");
}
#[test]
fn test_ins_mtsrdin() {
assert_asm!(0x7C8040E4, "mtsrdin r4, r8");
}
#[test]
fn test_ins_mulhd() {
assert_asm!(0x7C7CF892, "mulhd r3, r28, r31");
}
#[test]
fn test_ins_mulhdu() {
assert_asm!(0x7CBCF812, "mulhdu r5, r28, r31");
}
#[test]
fn test_ins_mulld() {
assert_asm!(0x7C6419D2, "mulld r3, r4, r3");
assert_asm!(0x7d6b49d2, "mulld r11, r11, r9");
}
#[test]
fn test_ins_rfid() {
assert_asm!(0x4c000024, "rfid");
}
#[test]
fn test_ins_rldcl() {
assert_asm!(0x780336D0, "rldcl r3, r0, r6, 27");
assert_asm!(0x78033010, "rotld r3, r0, r6");
}
#[test]
fn test_ins_rldcr() {
assert_asm!(0x78A345D2, "rldcr r3, r5, r8, 23");
}
#[test]
fn test_ins_rldic() {
assert_asm!(0x78C51928, "rldic r5, r6, 3, 36");
}
#[test]
fn test_ins_rldicl() {
assert_asm!(0x78c50020, "rldicl r5, r6, 0, 32");
assert_asm!(0x7bab07a0, "rldicl r11, r29, 0, 62");
}
#[test]
fn test_ins_rldicr() {
assert_asm!(0x7883ffe6, "rldicr r3, r4, 63, 63");
assert_asm!(0x798c37e4, "rldicr r12, r12, 6, 63");
assert_asm!(0x798c07c6, "rldicr r12, r12, 32, 31");
assert_asm!(0x798ccfe6, "rldicr r12, r12, 57, 63");
}
#[test]
fn test_ins_rldimi() {
assert_asm!(0x78a3a04e, "rldimi r3, r5, 52, 1");
assert_asm!(0x794b000e, "rldimi r11, r10, 32, 0");
assert_asm!(0x780331CC, "rldimi r3, r0, 6, 7");
}
#[test]
fn test_ins_slbia() {
assert_asm!(0x7c0003e4, "slbia");
}
#[test]
fn test_ins_slbie() {
assert_asm!(0x7C002B64, "slbie r5");
}
#[test]
fn test_ins_sld() {
assert_asm!(0x7d6a5036, "sld r10, r11, r10");
assert_asm!(0x7D034836, "sld r3, r8, r9");
}
#[test]
fn test_ins_srad() {
assert_asm!(0x7d0b5e34, "srad r11, r8, r11");
assert_asm!(0x7C033634, "srad r3, r0, r6");
}
#[test]
fn test_ins_sradi() {
assert_asm!(0x7cc4a674, "sradi r4, r6, 20");
assert_asm!(0x7d6b0676, "sradi r11, r11, 32");
}
#[test]
fn test_ins_srd() {
assert_asm!(0x7d0a4c36, "srd r10, r8, r9");
assert_asm!(0x7d675436, "srd r7, r11, r10");
assert_asm!(0x7C001C36, "srd r0, r0, r3");
assert_asm!(0x7C600436, "srd r0, r3, r0");
}
#[test]
fn test_ins_std() {
assert_asm!(0xfbe1fff0, "std r31, -0x10(r1)");
}
#[test]
fn test_ins_stdcx() {
assert_asm!(0x7CA749AD, "stdcx. r5, r7, r9");
assert_asm!(0x7fc0e9ad, "stdcx. r30, r0, r29");
}
#[test]
fn test_ins_stdu() {
assert_asm!(0xf9690009, "stdu r11, 0x8(r9)");
assert_asm!(0xf97ffff9, "stdu r11, -0x8(r31)");
}
#[test]
fn test_ins_stdux() {
assert_asm!(0x7C03316A, "stdux r0, r3, r6");
assert_asm!(0x7d5cc96a, "stdux r10, r28, r25");
}
#[test]
fn test_ins_stdx() {
assert_asm!(0x7CA7F92A, "stdx r5, r7, r31");
assert_asm!(0x7cc3212a, "stdx r6, r3, r4");
}
#[test]
fn test_ins_td() {
assert_asm!(0x7DC30088, "td 14, r3, r0");
}
#[test]
fn test_ins_tdi() {
assert_asm!(0x09830058, "tdi 12, r3, 0x58");
}
#[test]
fn test_vmx_dcbzl() {
assert_asm!(0x7c2327ec, "dcbzl r3, r4");
}

195
disasm/tests/test_ps.rs Normal file
View File

@ -0,0 +1,195 @@
use powerpc::{Argument, Extension, Extensions, Ins, Opcode, FPR, GPR};
const EXTENSIONS: Extensions = Extensions::from_extension(Extension::PairedSingles);
macro_rules! assert_asm {
($ins:ident, $disasm:literal) => {{
assert_eq!(format!("{}", $ins.simplified()), $disasm)
}};
($code:literal, $disasm:literal) => {{
let ins = Ins::new($code, EXTENSIONS);
assert_eq!(format!("{}", ins.simplified()), $disasm)
}};
}
#[test]
fn test_ins_dcbz_l() {
assert_asm!(0x10061FEC, "dcbz_l r6, r3");
}
#[test]
fn test_ins_psq_l() {
assert_asm!(0xE02500AC, "psq_l f1, 0xac(r5), 0, qr0");
}
#[test]
fn test_ins_psq_lu() {
assert_asm!(0xE5435010, "psq_lu f10, 0x10(r3), 0, qr5");
}
#[test]
fn test_ins_psq_lx() {
let ins = Ins::new(0x1000000C, EXTENSIONS);
assert_eq!(ins.op, Opcode::PsqLx);
// assert_eq!(
// ins.fields(),
// vec![
// frD(FPR(0)),
// rA(GPR(0)),
// rB(GPR(0)),
// ps_WX(OpaqueU(0)),
// ps_IX(GQR(0)),
// ]
// );
assert_eq!(
ins.defs(),
[Argument::FPR(FPR(0)), Argument::None, Argument::None, Argument::None, Argument::None]
);
assert_eq!(
ins.uses(),
[Argument::None, Argument::GPR(GPR(0)), Argument::None, Argument::None, Argument::None]
);
assert_asm!(0x1000000C, "psq_lx f0, r0, r0, 0, qr0");
}
#[test]
fn test_ins_psq_st() {
assert_asm!(0xF1230210, "psq_st f9, 0x210(r3), 0, qr0");
assert_asm!(0xF1238008, "psq_st f9, 0x8(r3), 1, qr0");
}
#[test]
fn test_ins_psq_stu() {
assert_asm!(0xF40A0020, "psq_stu f0, 0x20(r10), 0, qr0");
}
#[test]
fn test_ins_psq_stx() {
assert_asm!(0x13E1000E, "psq_stx f31, r1, r0, 0, qr0");
}
#[test]
fn test_ins_ps_abs() {
assert_asm!(0x10A03210, "ps_abs f5, f6");
}
#[test]
fn test_ins_ps_add() {
assert_asm!(0x1006382A, "ps_add f0, f6, f7");
}
#[test]
fn test_ins_ps_cmpo0() {
assert_asm!(0x10070840, "ps_cmpo0 cr0, f7, f1");
}
#[test]
fn test_ins_ps_cmpu0() {
assert_asm!(0x10003000, "ps_cmpu0 cr0, f0, f6");
}
#[test]
fn test_ins_ps_cmpu1() {
assert_asm!(0x10003080, "ps_cmpu1 cr0, f0, f6");
}
#[test]
fn test_ins_ps_madd() {
assert_asm!(0x112141FA, "ps_madd f9, f1, f7, f8");
}
#[test]
fn test_ins_ps_madds0() {
assert_asm!(0x10AC299C, "ps_madds0 f5, f12, f6, f5");
}
#[test]
fn test_ins_ps_madds1() {
assert_asm!(0x110640DE, "ps_madds1 f8, f6, f3, f8");
}
#[test]
fn test_ins_ps_merge00() {
assert_asm!(0x10400420, "ps_merge00 f2, f0, f0");
}
#[test]
fn test_ins_ps_merge01() {
assert_asm!(0x10400C60, "ps_merge01 f2, f0, f1");
}
#[test]
fn test_ins_ps_merge10() {
assert_asm!(0x104004A0, "ps_merge10 f2, f0, f0");
}
#[test]
fn test_ins_ps_merge11() {
assert_asm!(0x10AA14E0, "ps_merge11 f5, f10, f2");
}
#[test]
fn test_ins_ps_mr() {
assert_asm!(0x10200090, "ps_mr f1, f0");
}
#[test]
fn test_ins_ps_msub() {
assert_asm!(0x10A53778, "ps_msub f5, f5, f29, f6");
}
#[test]
fn test_ins_ps_mul() {
assert_asm!(0x10000032, "ps_mul f0, f0, f0");
}
#[test]
fn test_ins_ps_muls0() {
assert_asm!(0x100002D8, "ps_muls0 f0, f0, f11");
}
#[test]
fn test_ins_ps_muls1() {
assert_asm!(0x10A2005A, "ps_muls1 f5, f2, f1");
}
#[test]
fn test_ins_ps_nabs() {
assert_asm!(0x10803210, "ps_abs f4, f6");
}
#[test]
fn test_ins_ps_neg() {
assert_asm!(0x10E03850, "ps_neg f7, f7");
}
#[test]
fn test_ins_ps_nmadd() {
assert_asm!(0x10CB30FE, "ps_nmadd f6, f11, f3, f6");
}
#[test]
fn test_ins_ps_nmsub() {
assert_asm!(0x107E083C, "ps_nmsub f3, f30, f0, f1");
}
#[test]
fn test_ins_ps_sel() {
assert_asm!(0x106428EE, "ps_sel f3, f4, f3, f5");
}
#[test]
fn test_ins_ps_sub() {
assert_asm!(0x10A92828, "ps_sub f5, f9, f5");
}
#[test]
fn test_ins_ps_sum0() {
assert_asm!(0x10230854, "ps_sum0 f1, f3, f1, f1");
}
#[test]
fn test_ins_ps_sum1() {
assert_asm!(0x10A12956, "ps_sum1 f5, f1, f5, f5");
}

View File

@ -1,15 +1,41 @@
use ppc750cl::Ins; use powerpc::{Extension, Extensions, Ins};
const EXTENSIONS: Extensions = Extensions::from_extension(Extension::Vmx128);
macro_rules! assert_asm { macro_rules! assert_asm {
($ins:ident, $disasm:literal) => {{ ($ins:ident, $disasm:literal) => {{
assert_eq!(format!("{}", $ins.simplified()), $disasm) assert_eq!(format!("{}", $ins.simplified()), $disasm)
}}; }};
($code:literal, $disasm:literal) => {{ ($code:literal, $disasm:literal) => {{
let ins = Ins::new($code); let ins = Ins::new($code, EXTENSIONS);
assert_eq!(format!("{}", ins.simplified()), $disasm) assert_eq!(format!("{}", ins.simplified()), $disasm)
}}; }};
} }
#[test]
fn test_vmx_enables_altivec() {
let extensions = Extensions::from_extension(Extension::Vmx128);
assert!(extensions.contains(Extension::AltiVec));
}
#[test]
fn test_extensions_remove_vmx() {
let mut extensions = Extensions::from_extension(Extension::Vmx128);
extensions.remove(Extension::Vmx128);
assert!(!extensions.contains(Extension::Vmx128));
// Ensure AltiVec is still enabled
assert!(extensions.contains(Extension::AltiVec));
}
#[test]
fn test_extensions_remove_altivec() {
let mut extensions = Extensions::from_extension(Extension::Vmx128);
extensions.remove(Extension::AltiVec);
assert!(!extensions.contains(Extension::AltiVec));
// Ensure Vmx128 is disabled (AltiVec required)
assert!(!extensions.contains(Extension::Vmx128));
}
#[test] #[test]
fn test_vmx_lvewx128() { fn test_vmx_lvewx128() {
assert_asm!(0x1243388F, "lvewx128 v114, r3, r7"); assert_asm!(0x1243388F, "lvewx128 v114, r3, r7");

View File

@ -1,10 +1,10 @@
[package] [package]
name = "ppc750cl-fuzz" name = "powerpc-fuzz"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
authors = ["Richard Patel <me@terorie.dev>"] authors = ["Richard Patel <me@terorie.dev>"]
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
description = "Complete fuzzer for ppc750cl" description = "Complete fuzzer for powerpc"
repository.workspace = true repository.workspace = true
rust-version.workspace = true rust-version.workspace = true
publish = false publish = false
@ -12,4 +12,4 @@ publish = false
[dependencies] [dependencies]
clap = "4" clap = "4"
num_cpus = "1.16" num_cpus = "1.16"
ppc750cl = { path = "../disasm" } powerpc = { path = "../disasm" }

View File

@ -1,3 +1,4 @@
use powerpc::Extensions;
use std::io::Write; use std::io::Write;
use std::ops::Range; use std::ops::Range;
use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::atomic::{AtomicU32, Ordering};
@ -5,9 +6,9 @@ use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
fn main() { fn main() {
let matches = clap::Command::new("ppc750cl-fuzz") let matches = clap::Command::new("powerpc-fuzz")
.version("0.2.0") .version(env!("CARGO_PKG_VERSION"))
.about("Complete \"fuzzer\" for ppc750cl disassembler") .about("Complete \"fuzzer\" for powerpc disassembler")
.arg( .arg(
clap::Arg::new("threads") clap::Arg::new("threads")
.short('t') .short('t')
@ -101,9 +102,10 @@ impl Fuzzer {
let counter = Arc::clone(&self.counter); let counter = Arc::clone(&self.counter);
let range = self.range.clone(); let range = self.range.clone();
std::thread::spawn(move || { std::thread::spawn(move || {
let mut parsed = ppc750cl::ParsedIns::default(); let mut parsed = powerpc::ParsedIns::default();
for x in range.clone() { for x in range.clone() {
ppc750cl::Ins::new(x).parse_simplified(&mut parsed); powerpc::Ins::new(x, Extensions::from_bitmask(u32::MAX))
.parse_simplified(&mut parsed);
writeln!(&mut devnull, "{parsed}").unwrap(); writeln!(&mut devnull, "{parsed}").unwrap();
if x % (1 << 19) == 0 { if x % (1 << 19) == 0 {
counter.store(x, Ordering::Relaxed); counter.store(x, Ordering::Relaxed);

View File

@ -1,18 +1,20 @@
[package] [package]
name = "ppc750cl-genisa" name = "powerpc-genisa"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
authors.workspace = true authors.workspace = true
license.workspace = true license.workspace = true
description = "Rust code generator for ppc750cl" description = "Rust code generator for powerpc"
repository.workspace = true repository.workspace = true
rust-version.workspace = true rust-version.workspace = true
publish = false publish = false
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
indexmap = { version = "2.10", features = ["serde"] }
log = "0.4" log = "0.4"
num-traits = "0.2" num-traits = "0.2"
phf_codegen = "0.11"
prettyplease = "0.2" prettyplease = "0.2"
proc-macro2 = "1.0" proc-macro2 = "1.0"
quote = "1.0" quote = "1.0"
@ -20,5 +22,3 @@ serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.9" serde_yaml = "0.9"
simple_logger = "5.0" simple_logger = "5.0"
syn = { version = "2", default-features = false, features = ["full", "parsing"] } syn = { version = "2", default-features = false, features = ["full", "parsing"] }
phf = "0.11"
phf_codegen = "0.11"

View File

@ -102,7 +102,7 @@ pub fn gen_asm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
Ok(quote! { Ok(quote! {
#![allow(unused)] #![allow(unused)]
#![cfg_attr(rustfmt, rustfmt_skip)] #![cfg_attr(rustfmt, rustfmt_skip)]
#[comment = " Code generated by ppc750-genisa. DO NOT EDIT."] #[comment = " Code generated by powerpc-genisa. DO NOT EDIT."]
use crate::types::*; use crate::types::*;
pub type Arguments = [Argument; #max_args]; pub type Arguments = [Argument; #max_args];
#functions #functions

View File

@ -1,10 +1,15 @@
use crate::condition::{parse_conditions, replace_fields}; use crate::{
use crate::ident; condition::{parse_conditions, replace_fields},
use crate::isa::{modifiers_iter, modifiers_valid, HexLiteral, Isa, Opcode}; ident,
isa::{modifiers_iter, modifiers_valid, HexLiteral, Isa, Opcode},
};
use anyhow::{bail, ensure, Result}; use anyhow::{bail, ensure, Result};
use proc_macro2::{Literal, TokenStream}; use proc_macro2::{Ident, Literal, TokenStream};
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use std::collections::HashMap; use std::{
collections::{btree_map, BTreeMap, HashMap},
hash::{DefaultHasher, Hash, Hasher},
};
pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> { pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
// The entry table allows us to quickly find the range of possible opcodes // The entry table allows us to quickly find the range of possible opcodes
@ -59,7 +64,19 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
let pattern = HexLiteral(opcode.pattern); let pattern = HexLiteral(opcode.pattern);
let enum_idx = Literal::u16_unsuffixed(idx as u16); let enum_idx = Literal::u16_unsuffixed(idx as u16);
let name = &opcode.name; let name = &opcode.name;
opcode_patterns.extend(quote! { (#bitmask, #pattern), }); let comment = format!(" {}", name);
let extension =
isa.extensions.iter().find(|(_, e)| e.opcodes.iter().any(|o| o.name == opcode.name));
let initializer = if let Some((id, _)) = extension {
let ident = format_ident!("{id}");
quote! { OpcodePattern::extension(#bitmask, #pattern, Extension::#ident) }
} else {
quote! { OpcodePattern::base(#bitmask, #pattern) }
};
opcode_patterns.extend(quote! {
#[comment = #comment]
#initializer,
});
opcode_names.extend(quote! { #name, }); opcode_names.extend(quote! { #name, });
let doc = opcode.doc(); let doc = opcode.doc();
let variant = opcode.variant(); let variant = opcode.variant();
@ -244,9 +261,12 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
let mut defs_uses_functions = TokenStream::new(); let mut defs_uses_functions = TokenStream::new();
let mut defs_refs = TokenStream::new(); let mut defs_refs = TokenStream::new();
let mut uses_refs = TokenStream::new(); let mut uses_refs = TokenStream::new();
// Deduplicate equivalent functions
let mut hash_to_fn = BTreeMap::<u64, Ident>::new();
for opcode in &sorted_ops { for opcode in &sorted_ops {
let mut defs = TokenStream::new(); let mut defs = TokenStream::new();
let mut uses = TokenStream::new();
let mut defs_count = 0; let mut defs_count = 0;
for def in &opcode.defs { for def in &opcode.defs {
if isa.find_field(def).is_some_and(|f| f.arg.is_none()) { if isa.find_field(def).is_some_and(|f| f.arg.is_none()) {
@ -256,6 +276,8 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
defs.extend(quote! { #arg, }); defs.extend(quote! { #arg, });
defs_count += 1; defs_count += 1;
} }
let mut uses = TokenStream::new();
let mut use_count = 0; let mut use_count = 0;
for use_ in &opcode.uses { for use_ in &opcode.uses {
if let Some(use_) = use_.strip_suffix(".nz") { if let Some(use_) = use_.strip_suffix(".nz") {
@ -278,10 +300,23 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
defs.extend(quote! { Argument::None, }); defs.extend(quote! { Argument::None, });
} }
let defs_name = format_ident!("defs_{}", opcode.ident()); let defs_name = format_ident!("defs_{}", opcode.ident());
defs_uses_functions.extend(quote! {
fn #defs_name(out: &mut Arguments, ins: Ins) { *out = [#defs]; } let mut hasher = DefaultHasher::default();
}); opcode.defs.hash(&mut hasher);
defs_refs.extend(quote! { #defs_name, }); let defs_hash = hasher.finish();
match hash_to_fn.entry(defs_hash) {
btree_map::Entry::Vacant(e) => {
e.insert(defs_name.clone());
defs_uses_functions.extend(quote! {
fn #defs_name(out: &mut Arguments, ins: Ins) { *out = [#defs]; }
});
defs_refs.extend(quote! { #defs_name, });
}
btree_map::Entry::Occupied(e) => {
let ident = e.get();
defs_refs.extend(quote! { #ident, });
}
}
} else { } else {
defs_refs.extend(quote! { defs_uses_empty, }); defs_refs.extend(quote! { defs_uses_empty, });
} }
@ -291,10 +326,23 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
uses.extend(quote! { Argument::None, }); uses.extend(quote! { Argument::None, });
} }
let uses_name = format_ident!("uses_{}", opcode.ident()); let uses_name = format_ident!("uses_{}", opcode.ident());
defs_uses_functions.extend(quote! {
fn #uses_name(out: &mut Arguments, ins: Ins) { *out = [#uses]; } let mut hasher = DefaultHasher::default();
}); opcode.uses.hash(&mut hasher);
uses_refs.extend(quote! { #uses_name, }); let uses_hash = hasher.finish();
match hash_to_fn.entry(uses_hash) {
btree_map::Entry::Vacant(e) => {
e.insert(uses_name.clone());
defs_uses_functions.extend(quote! {
fn #uses_name(out: &mut Arguments, ins: Ins) { *out = [#uses]; }
});
uses_refs.extend(quote! { #uses_name, });
}
btree_map::Entry::Occupied(e) => {
let ident = e.get();
uses_refs.extend(quote! { #ident, });
}
}
} else { } else {
uses_refs.extend(quote! { defs_uses_empty, }); uses_refs.extend(quote! { defs_uses_empty, });
} }
@ -308,19 +356,96 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
none_args.extend(quote! { Argument::None, }); none_args.extend(quote! { Argument::None, });
} }
let extension_variants = isa
.extensions
.iter()
.map(|(id, ext)| {
let ident = format_ident!("{id}");
let desc = format!(" {}", ext.name);
quote! {
#[doc = #desc]
#ident,
}
})
.collect::<Vec<_>>();
let extension_requires = isa
.extensions
.iter()
.filter_map(|(id, ext)| {
if ext.requires.is_empty() {
return None;
}
let requires = ext
.requires
.iter()
.map(|parent| {
let ident = format_ident!("{parent}");
quote! { Extension::#ident.bitmask() }
})
.collect::<Vec<_>>();
let ident = format_ident!("{id}");
Some(quote! { Extension::#ident => #(#requires)|*, })
})
.collect::<Vec<_>>();
let extension_names = isa
.extensions
.iter()
.map(|(id, ext)| {
let ident = format_ident!("{id}");
let name = &ext.name;
Some(quote! { Extension::#ident => #name, })
})
.collect::<Vec<_>>();
let extensions = quote! {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Extension {
#(#extension_variants)*
}
impl Extension {
#[inline]
pub const fn bitmask(self) -> u32 {
(1 << (self as u32)) | match self {
#(#extension_requires)*
_ => 0,
}
}
pub const fn name(self) -> &'static str {
match self {
#(#extension_names)*
}
}
}
};
let entries_count = Literal::usize_unsuffixed(entries.len()); let entries_count = Literal::usize_unsuffixed(entries.len());
let opcode_count = Literal::usize_unsuffixed(sorted_ops.len()); let opcode_count = Literal::usize_unsuffixed(sorted_ops.len());
let max_args = Literal::usize_unsuffixed(max_args); let max_args = Literal::usize_unsuffixed(max_args);
Ok(quote! { Ok(quote! {
#![allow(unused)] #![allow(unused)]
#![cfg_attr(rustfmt, rustfmt_skip)] #![cfg_attr(rustfmt, rustfmt_skip)]
#[comment = " Code generated by ppc750-genisa. DO NOT EDIT."] #[comment = " Code generated by powerpc-genisa. DO NOT EDIT."]
use crate::disasm::*; use crate::disasm::*;
#extensions
#[doc = " The entry table allows us to quickly find the range of possible opcodes for a"] #[doc = " The entry table allows us to quickly find the range of possible opcodes for a"]
#[doc = " given 6-bit prefix. 2*64 bytes should fit in a cache line (or two)."] #[doc = " given 6-bit prefix. 2*64 bytes should fit in a cache line (or two)."]
static OPCODE_ENTRIES: [(u16, u16); #entries_count] = [#opcode_entries]; static OPCODE_ENTRIES: [(u16, u16); #entries_count] = [#opcode_entries];
#[derive(Copy, Clone)]
struct OpcodePattern {
bitmask: u32,
pattern: u32,
extensions: Extensions,
}
impl OpcodePattern {
const fn base(bitmask: u32, pattern: u32) -> Self {
Self { bitmask, pattern, extensions: Extensions::none() }
}
const fn extension(bitmask: u32, pattern: u32, extension: Extension) -> Self {
Self { bitmask, pattern, extensions: Extensions::from_extension(extension) }
}
}
#[doc = " The bitmask and pattern for each opcode."] #[doc = " The bitmask and pattern for each opcode."]
static OPCODE_PATTERNS: [(u32, u32); #opcode_count] = [#opcode_patterns]; static OPCODE_PATTERNS: [OpcodePattern; #opcode_count] = [#opcode_patterns];
#[doc = " The name of each opcode."] #[doc = " The name of each opcode."]
static OPCODE_NAMES: [&str; #opcode_count] = [#opcode_names]; static OPCODE_NAMES: [&str; #opcode_count] = [#opcode_names];
@ -334,17 +459,15 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
#opcode_enum #opcode_enum
} }
impl Opcode { impl Opcode {
#[inline]
pub fn mnemonic(self) -> &'static str { pub fn mnemonic(self) -> &'static str {
OPCODE_NAMES.get(self as usize).copied().unwrap_or("<illegal>") OPCODE_NAMES.get(self as usize).copied().unwrap_or("<illegal>")
} }
#[inline] pub fn detect(code: u32, extensions: Extensions) -> Self {
pub fn detect(code: u32) -> Self {
let entry = OPCODE_ENTRIES[(code >> 26) as usize]; let entry = OPCODE_ENTRIES[(code >> 26) as usize];
for i in entry.0..entry.1 { for i in entry.0..entry.1 {
let pattern = OPCODE_PATTERNS[i as usize]; let op = OPCODE_PATTERNS[i as usize];
if (code & pattern.0) == pattern.1 { if extensions.contains_all(op.extensions) && (code & op.bitmask) == op.pattern {
#[comment = " Safety: The enum is repr(u16) and the value is within the enum's range"] #[comment = " Safety: The enum is repr(u16) and the value is within the enum's range"]
return unsafe { core::mem::transmute::<u16, Opcode>(i) }; return unsafe { core::mem::transmute::<u16, Opcode>(i) };
} }
@ -380,16 +503,14 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
type MnemonicFunction = fn(&mut ParsedIns, Ins); type MnemonicFunction = fn(&mut ParsedIns, Ins);
#mnemonic_functions #mnemonic_functions
static BASIC_MNEMONICS: [MnemonicFunction; #opcode_count] = [#basic_functions_ref]; static BASIC_MNEMONICS: [MnemonicFunction; #opcode_count] = [#basic_functions_ref];
#[inline] pub(crate) fn parse_basic(out: &mut ParsedIns, ins: Ins) {
pub fn parse_basic(out: &mut ParsedIns, ins: Ins) {
match BASIC_MNEMONICS.get(ins.op as usize) { match BASIC_MNEMONICS.get(ins.op as usize) {
Some(f) => f(out, ins), Some(f) => f(out, ins),
None => mnemonic_illegal(out, ins), None => mnemonic_illegal(out, ins),
} }
} }
static SIMPLIFIED_MNEMONICS: [MnemonicFunction; #opcode_count] = [#simplified_functions_ref]; static SIMPLIFIED_MNEMONICS: [MnemonicFunction; #opcode_count] = [#simplified_functions_ref];
#[inline] pub(crate) fn parse_simplified(out: &mut ParsedIns, ins: Ins) {
pub fn parse_simplified(out: &mut ParsedIns, ins: Ins) {
match SIMPLIFIED_MNEMONICS.get(ins.op as usize) { match SIMPLIFIED_MNEMONICS.get(ins.op as usize) {
Some(f) => f(out, ins), Some(f) => f(out, ins),
None => mnemonic_illegal(out, ins), None => mnemonic_illegal(out, ins),
@ -399,16 +520,14 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
type DefsUsesFunction = fn(&mut Arguments, Ins); type DefsUsesFunction = fn(&mut Arguments, Ins);
#defs_uses_functions #defs_uses_functions
static DEFS_FUNCTIONS: [DefsUsesFunction; #opcode_count] = [#defs_refs]; static DEFS_FUNCTIONS: [DefsUsesFunction; #opcode_count] = [#defs_refs];
#[inline] pub(crate) fn parse_defs(out: &mut Arguments, ins: Ins) {
pub fn parse_defs(out: &mut Arguments, ins: Ins) {
match DEFS_FUNCTIONS.get(ins.op as usize) { match DEFS_FUNCTIONS.get(ins.op as usize) {
Some(f) => f(out, ins), Some(f) => f(out, ins),
None => defs_uses_empty(out, ins), None => defs_uses_empty(out, ins),
} }
} }
static USES_FUNCTIONS: [DefsUsesFunction; #opcode_count] = [#uses_refs]; static USES_FUNCTIONS: [DefsUsesFunction; #opcode_count] = [#uses_refs];
#[inline] pub(crate) fn parse_uses(out: &mut Arguments, ins: Ins) {
pub fn parse_uses(out: &mut Arguments, ins: Ins) {
match USES_FUNCTIONS.get(ins.op as usize) { match USES_FUNCTIONS.get(ins.op as usize) {
Some(f) => f(out, ins), Some(f) => f(out, ins),
None => defs_uses_empty(out, ins), None => defs_uses_empty(out, ins),

View File

@ -1,7 +1,8 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::{fs::File, path::Path, str::FromStr}; use std::{fs::File, path::Path, str::FromStr};
use anyhow::{Context, Result}; use anyhow::{bail, Context, Result};
use indexmap::IndexMap;
use num_traits::PrimInt; use num_traits::PrimInt;
use proc_macro2::{Ident, Span, TokenStream}; use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, ToTokens}; use quote::{format_ident, ToTokens};
@ -11,8 +12,55 @@ use serde::{Deserialize, Deserializer, Serialize};
pub fn load_isa(path: &Path) -> Result<Isa> { pub fn load_isa(path: &Path) -> Result<Isa> {
let yaml_file = let yaml_file =
File::open(path).with_context(|| format!("Failed to open file {}", path.display()))?; File::open(path).with_context(|| format!("Failed to open file {}", path.display()))?;
let isa: Isa = serde_yaml::from_reader(yaml_file) let mut isa: Isa = serde_yaml::from_reader(yaml_file)
.with_context(|| format!("While parsing file {}", path.display()))?; .with_context(|| format!("While parsing file {}", path.display()))?;
// Merge in all extensions
for extension in isa.extensions.values() {
for field in &extension.fields {
if isa.find_field(&field.name).is_some() {
bail!(
"Field {} already exists (while applying extension {})",
field.name,
extension.name
);
} else {
isa.fields.push(field.clone());
}
}
for modifier in &extension.modifiers {
if isa.find_modifier(&modifier.name).is_some() {
bail!(
"Modifier {} already exists (while applying extension {})",
modifier.name,
extension.name
);
} else {
isa.modifiers.push(modifier.clone());
}
}
for opcode in &extension.opcodes {
if isa.find_opcode(&opcode.name).is_some() {
bail!(
"Opcode {} already exists (while applying extension {})",
opcode.name,
extension.name
);
} else {
isa.opcodes.push(opcode.clone());
}
}
for mnemonic in &extension.mnemonics {
if isa.find_mnemonic(&mnemonic.name).is_some() {
bail!(
"Mnemonic {} already exists (while applying extension {})",
mnemonic.name,
extension.name
);
} else {
isa.mnemonics.push(mnemonic.clone());
}
}
}
Ok(isa) Ok(isa)
} }
@ -23,6 +71,18 @@ pub struct Isa {
pub modifiers: Vec<Modifier>, pub modifiers: Vec<Modifier>,
pub opcodes: Vec<Opcode>, pub opcodes: Vec<Opcode>,
pub mnemonics: Vec<Mnemonic>, pub mnemonics: Vec<Mnemonic>,
pub extensions: IndexMap<String, Extension>,
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
#[serde(default)]
pub struct Extension {
pub name: String,
pub requires: Vec<String>,
pub fields: Vec<Field>,
pub modifiers: Vec<Modifier>,
pub opcodes: Vec<Opcode>,
pub mnemonics: Vec<Mnemonic>,
} }
impl Isa { impl Isa {
@ -37,6 +97,10 @@ impl Isa {
pub fn find_opcode(&self, name: &str) -> Option<&Opcode> { pub fn find_opcode(&self, name: &str) -> Option<&Opcode> {
self.opcodes.iter().find(|o| o.name == name) self.opcodes.iter().find(|o| o.name == name)
} }
pub fn find_mnemonic(&self, name: &str) -> Option<&Mnemonic> {
self.mnemonics.iter().find(|m| m.name == name)
}
} }
#[derive(Deserialize, Serialize, Clone, Debug, Default)] #[derive(Deserialize, Serialize, Clone, Debug, Default)]
@ -222,6 +286,7 @@ pub fn modifiers_iter<'a>(
} }
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default)]
#[repr(transparent)]
pub struct BitRange(pub (u8, u8)); pub struct BitRange(pub (u8, u8));
impl BitRange { impl BitRange {
@ -231,42 +296,42 @@ impl BitRange {
} }
#[inline] #[inline]
pub fn start(&self) -> u8 { pub fn start(self) -> u8 {
self.0 .0 self.0 .0
} }
#[inline] #[inline]
pub fn end(&self) -> u8 { pub fn end(self) -> u8 {
self.0 .1 self.0 .1
} }
/// Calculate the mask from the range /// Calculate the mask from the range
#[inline] #[inline]
pub fn mask(&self) -> u32 { pub fn mask(self) -> u32 {
self.max_value() << self.shift() self.max_value() << self.shift()
} }
/// Number of bits to shift /// Number of bits to shift
#[inline] #[inline]
pub fn shift(&self) -> u8 { pub fn shift(self) -> u8 {
32 - self.end() 32 - self.end()
} }
/// Number of bits in the range /// Number of bits in the range
#[inline] #[inline]
pub fn len(&self) -> u8 { pub fn len(self) -> u8 {
self.end() - self.start() self.end() - self.start()
} }
/// Shift and mask a value according to the range /// Shift and mask a value according to the range
#[inline] #[inline]
pub fn shift_value(&self, value: u32) -> u32 { pub fn shift_value(self, value: u32) -> u32 {
(value & self.max_value()) << self.shift() (value & self.max_value()) << self.shift()
} }
/// Calculate the maximum value that can be represented by the range /// Calculate the maximum value that can be represented by the range
#[inline] #[inline]
pub fn max_value(&self) -> u32 { pub fn max_value(self) -> u32 {
(1 << self.len()) - 1 (1 << self.len()) - 1
} }
} }
@ -308,13 +373,13 @@ pub struct SplitBitRange(pub Vec<BitRange>);
impl SplitBitRange { impl SplitBitRange {
#[inline] #[inline]
pub fn end(&self) -> u8 { pub fn end(&self) -> u8 {
self.0.iter().map(|r| r.end()).max().unwrap_or(0) self.iter().map(BitRange::end).max().unwrap_or(0)
} }
/// Calculate the mask from the range /// Calculate the mask from the range
#[inline] #[inline]
pub fn mask(&self) -> u32 { pub fn mask(&self) -> u32 {
self.0.iter().map(|r| r.mask()).fold(0, |acc, m| acc | m) self.iter().map(BitRange::mask).fold(0, |acc, m| acc | m)
} }
/// Number of bits to shift /// Number of bits to shift
@ -326,14 +391,14 @@ impl SplitBitRange {
/// Number of bits in the range /// Number of bits in the range
#[inline] #[inline]
pub fn len(&self) -> u8 { pub fn len(&self) -> u8 {
self.0.iter().map(|r| r.len()).sum() self.iter().map(BitRange::len).sum()
} }
/// Shift and mask a value according to the range /// Shift and mask a value according to the range
#[inline] #[inline]
pub fn shift_value(&self, mut value: u32) -> u32 { pub fn shift_value(&self, mut value: u32) -> u32 {
let mut result = 0; let mut result = 0;
for range in self.0.iter().rev() { for range in self.iter().rev() {
result |= range.shift_value(value); result |= range.shift_value(value);
value >>= range.len(); value >>= range.len();
} }
@ -345,6 +410,12 @@ impl SplitBitRange {
pub fn max_value(&self) -> u32 { pub fn max_value(&self) -> u32 {
(1 << self.len()) - 1 (1 << self.len()) - 1
} }
/// Create an iterator over the contained bit ranges
#[inline]
pub fn iter(&self) -> impl DoubleEndedIterator<Item = BitRange> + use<'_> {
self.0.iter().copied()
}
} }
impl<'de> Deserialize<'de> for SplitBitRange { impl<'de> Deserialize<'de> for SplitBitRange {

5332
isa.yaml

File diff suppressed because it is too large Load Diff