API updates and cleanup

This commit is contained in:
Luke Street 2024-03-21 21:31:30 -06:00
parent c4af15ddc2
commit d31bf75009
9 changed files with 4806 additions and 4928 deletions

View File

@ -4,7 +4,6 @@ resolver = "2"
[profile.release]
panic = "abort"
opt-level = "z"
[profile.release-lto]
inherits = "release"

File diff suppressed because it is too large Load Diff

View File

@ -4,11 +4,11 @@ use core::{
};
use crate::generated::{
Arguments, Opcode, BASE_MNEMONICS, DEFS_FUNCTIONS, SIMPLIFIED_MNEMONICS, USES_FUNCTIONS,
parse_basic, parse_defs, parse_simplified, parse_uses, Arguments, Opcode, EMPTY_ARGS,
};
/// A PowerPC 750CL instruction.
#[derive(Default, Clone, Debug, Eq, PartialEq)]
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
pub struct Ins {
pub code: u32,
pub op: Opcode,
@ -21,23 +21,59 @@ impl Ins {
}
/// Parse the instruction into a simplified mnemonic, if any match.
pub fn simplified(self) -> SimplifiedIns {
SimplifiedIns::new(self)
#[inline]
pub fn parse_simplified(self, out: &mut ParsedIns) {
parse_simplified(out, self)
}
/// Returns the simplified form of the instruction, if any match.
#[inline]
pub fn simplified(self) -> ParsedIns {
let mut out = ParsedIns::new();
parse_simplified(&mut out, self);
out
}
/// Parse the instruction into its basic form.
pub fn basic_form(self) -> SimplifiedIns {
SimplifiedIns::basic_form(self)
#[inline]
pub fn parse_basic(self, out: &mut ParsedIns) {
parse_basic(out, self)
}
/// Returns the basic form of the instruction.
#[inline]
pub fn basic(self) -> ParsedIns {
let mut out = ParsedIns::new();
parse_basic(&mut out, self);
out
}
/// Emits all registers defined by the instruction into the given argument list.
#[inline]
pub fn parse_defs(self, out: &mut Arguments) {
parse_defs(out, self)
}
/// Returns all registers defined by the instruction.
pub fn defs(&self) -> Arguments {
DEFS_FUNCTIONS[self.op as usize](self)
#[inline]
pub fn defs(self) -> Arguments {
let mut out = Arguments::default();
parse_defs(&mut out, self);
out
}
/// Emits all registers used by the instruction into the given argument list.
#[inline]
pub fn parse_uses(self, out: &mut Arguments) {
parse_uses(out, self)
}
/// Returns all registers used by the instruction.
pub fn uses(&self) -> Arguments {
USES_FUNCTIONS[self.op as usize](self)
#[inline]
pub fn uses(self) -> Arguments {
let mut out = Arguments::default();
parse_uses(&mut out, self);
out
}
/// Returns the relative branch offset of the instruction, if any.
@ -258,7 +294,7 @@ impl From<u8> for OpaqueU {
}
}
#[derive(Debug, Default, Clone, Eq, PartialEq)]
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub enum Argument {
#[default]
None,
@ -296,33 +332,38 @@ impl Display for Argument {
}
}
/// A simplified PowerPC 750CL instruction.
pub struct SimplifiedIns {
pub ins: Ins,
/// A parsed PowerPC 750CL instruction.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParsedIns {
pub mnemonic: &'static str,
pub args: Arguments,
}
impl SimplifiedIns {
pub fn new(ins: Ins) -> Self {
let (mnemonic, args) = SIMPLIFIED_MNEMONICS[ins.op as usize](&ins);
Self { ins, mnemonic, args }
}
pub fn basic_form(ins: Ins) -> Self {
let (mnemonic, args) = BASE_MNEMONICS[ins.op as usize](&ins);
Self { ins, mnemonic, args }
impl Default for ParsedIns {
fn default() -> Self {
Self::new()
}
}
impl Display for SimplifiedIns {
impl ParsedIns {
/// An empty parsed instruction.
pub const fn new() -> Self {
Self { mnemonic: "<illegal>", args: EMPTY_ARGS }
}
/// Returns an iterator over the arguments of the instruction,
/// stopping at the first [Argument::None].
#[inline]
pub fn args_iter(&self) -> impl Iterator<Item = &Argument> {
self.args.iter().take_while(|x| !matches!(x, Argument::None))
}
}
impl Display for ParsedIns {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.mnemonic)?;
let mut writing_offset = false;
for (i, argument) in self.args.iter().enumerate() {
if matches!(argument, Argument::None) {
break;
}
for (i, argument) in self.args_iter().enumerate() {
if i == 0 {
write!(f, " ")?;
} else if !writing_offset {
@ -332,9 +373,7 @@ impl Display for SimplifiedIns {
if let Argument::Offset(_) = argument {
write!(f, "(")?;
writing_offset = true;
continue;
}
if writing_offset {
} else if writing_offset {
write!(f, ")")?;
writing_offset = false;
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,22 +1,22 @@
use ppc750cl::{Argument, Ins, Opcode, SimplifiedIns, FPR, GPR};
use ppc750cl::{Argument, Ins, Opcode, FPR, GPR};
macro_rules! assert_asm {
($ins:ident, $disasm:literal) => {{
assert_eq!(format!("{}", SimplifiedIns::new($ins)), $disasm)
assert_eq!(format!("{}", $ins.simplified()), $disasm)
}};
($code:literal, $disasm:literal) => {{
let ins = Ins::new($code);
assert_eq!(format!("{}", SimplifiedIns::new(ins)), $disasm)
assert_eq!(format!("{}", ins.simplified()), $disasm)
}};
}
macro_rules! assert_basic {
($ins:ident, $disasm:literal) => {{
assert_eq!(format!("{}", SimplifiedIns::basic_form($ins)), $disasm)
assert_eq!(format!("{}", $ins.basic_form()), $disasm)
}};
($code:literal, $disasm:literal) => {{
let ins = Ins::new($code);
assert_eq!(format!("{}", SimplifiedIns::basic_form(ins)), $disasm)
assert_eq!(format!("{}", ins.basic()), $disasm)
}};
}

View File

@ -5,8 +5,6 @@ use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use ppc750cl::Ins;
fn main() {
let matches = clap::Command::new("ppc750cl-fuzz")
.version("0.2.0")
@ -56,9 +54,11 @@ impl MultiFuzzer {
fn dispatch_progress_monitor(&self) {
let this = self.clone();
std::thread::spawn(move || {
let start = Instant::now();
let mut last = 0u32;
loop {
std::thread::sleep(Duration::from_secs(1));
let elapsed = start.elapsed();
let mut now = 0u32;
for thread in &this.threads {
now += thread.counter.load(Ordering::Relaxed) - thread.range.start;
@ -66,7 +66,8 @@ impl MultiFuzzer {
let per_second = now - last;
last = now;
let progress = 100f32 * ((now as f32) / (0x1_0000_0000u64 as f32));
println!("{}/s\t{:05.2}%\tn=0x{:08x}", per_second, progress, now);
let avg = now as f32 / elapsed.as_secs_f32() / this.threads.len() as f32;
println!("{}/s\t{:05.2}%\tn=0x{:08x} (avg {}/s)", per_second, progress, now, avg);
}
});
}
@ -99,9 +100,10 @@ impl Fuzzer {
let counter = Arc::clone(&self.counter);
let range = self.range.clone();
std::thread::spawn(move || {
let mut parsed = ppc750cl::ParsedIns::default();
for x in range.clone() {
let ins = Ins::new(x);
writeln!(&mut devnull, "{}", ins.simplified()).unwrap();
ppc750cl::Ins::new(x).parse_simplified(&mut parsed);
writeln!(&mut devnull, "{}", parsed).unwrap();
if x % (1 << 19) == 0 {
counter.store(x, Ordering::Relaxed);
}

View File

@ -6,7 +6,7 @@ use crate::isa::{
use anyhow::{bail, Context, Result};
use proc_macro2::{Literal, TokenStream};
use quote::{format_ident, quote};
use std::collections::HashMap;
use std::collections::BTreeMap;
pub fn gen_asm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
let mut functions = TokenStream::new();
@ -20,7 +20,7 @@ pub fn gen_asm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
});
}
let mut mnemonic_map = HashMap::<String, Vec<Mnemonic>>::new();
let mut mnemonic_map = BTreeMap::<String, Vec<Mnemonic>>::new();
for mnemonic in &isa.mnemonics {
mnemonic_map.entry(mnemonic.name.clone()).or_default().push(mnemonic.clone());
}
@ -88,11 +88,12 @@ pub fn gen_asm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
Ok(quote! {
#![allow(unused)]
#![cfg_attr(rustfmt, rustfmt_skip)]
#[comment = " Code generated by ppc750-genisa. DO NOT EDIT."]
use crate::types::*;
pub type Arguments = [Argument; #max_args];
#functions
type MnemonicFn = fn(&Arguments, u32) -> Result<u32, ArgumentError>;
const MNEMONIC_MAP: phf::Map<&'static str, (MnemonicFn, u32)> = #func_map;
static MNEMONIC_MAP: phf::Map<&'static str, (MnemonicFn, u32)> = #func_map;
pub fn assemble(mnemonic: &str, args: &Arguments) -> Result<u32, ArgumentError> {
if let Some(&(fn_ptr, modifiers)) = MNEMONIC_MAP.get(mnemonic) {
fn_ptr(args, modifiers)

View File

@ -5,6 +5,7 @@ use anyhow::{bail, ensure, Result};
use proc_macro2::{Literal, TokenStream};
use quote::{format_ident, quote};
use std::collections::HashMap;
use std::num::NonZeroU8;
pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
// The entry table allows us to quickly find the range of possible opcodes
@ -79,13 +80,11 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
continue;
}
let mut sign_bit = bits.len() - 1;
let mut shift_right = bits.shift();
let mut shift_left = field.shift_left;
if shift_right == shift_left {
// Optimization: these cancel each other out
// Adjust subsequent operations to operate on the full value
sign_bit += shift_left;
shift_right = 0;
shift_left = 0;
}
@ -101,20 +100,20 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
// Determine the smallest integer type that can hold the value
let num_bits = bits.len() + field.shift_left;
let (out_type, cast) = match (num_bits, field.signed) {
(1..=8, false) => (ident!(u8), true),
(9..=16, false) => (ident!(u16), true),
(17..=32, false) => (ident!(u32), false),
(1..=8, true) => (ident!(i8), true),
(9..=16, true) => (ident!(i16), true),
(17..=32, true) => (ident!(i32), true),
let (out_type, cast, sign_shift) = match (num_bits, field.signed) {
(1..=8, false) => (ident!(u8), true, None),
(9..=16, false) => (ident!(u16), true, None),
(17..=32, false) => (ident!(u32), false, None),
(1..=8, true) => (ident!(i8), true, NonZeroU8::new(8 - num_bits)),
(9..=16, true) => (ident!(i16), true, NonZeroU8::new(16 - num_bits)),
(17..=32, true) => (ident!(i32), true, NonZeroU8::new(32 - num_bits)),
(v, _) => bail!("Unsupported field size {v}"),
};
// Handle sign extension
if field.signed {
let sign_value = HexLiteral(1 << sign_bit);
inner = quote! { ((#inner) ^ #sign_value).wrapping_sub(#sign_value) as #out_type };
if let Some(sign_shift) = sign_shift {
let sign_shift = Literal::u8_unsuffixed(sign_shift.get());
inner = quote! { (((#inner) << #sign_shift) as #out_type) >> #sign_shift };
} else if cast {
inner = quote! { (#inner) as #out_type };
}
@ -162,7 +161,7 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
// Generate simplified mnemonics
let mut mnemonic_functions = TokenStream::new();
let mut base_functions_ref = TokenStream::new();
let mut basic_functions_ref = TokenStream::new();
let mut simplified_functions_ref = TokenStream::new();
for opcode in &sorted_ops {
let mnemonics =
@ -186,47 +185,43 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
)?;
mnemonic_conditions.extend(quote! {
if #(#conditions)&&* {
return #inner;
*out = #inner;
return;
}
});
}
// Fallback to the base opcode name if no mnemonic matches
// Fallback to the basic opcode name if no mnemonic matches
let inner =
gen_mnemonic(&opcode.name, &opcode.args, &opcode.modifiers, isa, max_args, None)?;
let base_name = format_ident!("base_{}", opcode.ident());
let basic_name = format_ident!("basic_{}", opcode.ident());
if mnemonics.is_empty() {
mnemonic_functions.extend(quote! {
const fn #base_name(ins: &Ins) -> (&'static str, Arguments) {
#inner
fn #basic_name(out: &mut ParsedIns, ins: Ins) {
*out = #inner;
}
});
base_functions_ref.extend(quote! { #base_name, });
simplified_functions_ref.extend(quote! { #base_name, });
basic_functions_ref.extend(quote! { #basic_name, });
simplified_functions_ref.extend(quote! { #basic_name, });
} else {
let simplified_name = format_ident!("simplified_{}", opcode.ident());
mnemonic_functions.extend(quote! {
#[inline(always)]
const fn #base_name(ins: &Ins) -> (&'static str, Arguments) {
#inner
fn #basic_name(out: &mut ParsedIns, ins: Ins) {
*out = #inner;
}
const fn #simplified_name(ins: &Ins) -> (&'static str, Arguments) {
fn #simplified_name(out: &mut ParsedIns, ins: Ins) {
#mnemonic_conditions
#base_name(ins)
#basic_name(out, ins)
}
});
base_functions_ref.extend(quote! { #base_name, });
basic_functions_ref.extend(quote! { #basic_name, });
simplified_functions_ref.extend(quote! { #simplified_name, });
}
}
let mut none_args = TokenStream::new();
for _ in 0..max_args {
none_args.extend(quote! { Argument::None, });
}
mnemonic_functions.extend(quote! {
const fn mnemonic_illegal(_ins: &Ins) -> (&'static str, Arguments) {
("<illegal>", [#none_args])
}
fn mnemonic_illegal(out: &mut ParsedIns, _ins: Ins) {
*out = ParsedIns::new();
}
});
// TODO rework defs/uses to account for modifiers and special registers (CTR, LR, etc)
@ -245,9 +240,6 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
defs.extend(quote! { #arg, });
defs_count += 1;
}
for _ in defs_count..max_args {
defs.extend(quote! { Argument::None, });
}
let mut use_count = 0;
for use_ in &opcode.uses {
if let Some(use_) = use_.strip_suffix(".nz") {
@ -264,44 +256,65 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
uses.extend(quote! { #arg, });
use_count += 1;
}
for _ in use_count..max_args {
uses.extend(quote! { Argument::None, });
if defs_count > 0 {
for _ in defs_count..max_args {
defs.extend(quote! { Argument::None, });
}
let defs_name = format_ident!("defs_{}", opcode.ident());
defs_uses_functions.extend(quote! {
fn #defs_name(out: &mut Arguments, ins: Ins) { *out = [#defs]; }
});
defs_refs.extend(quote! { #defs_name, });
} else {
defs_refs.extend(quote! { defs_uses_empty, });
}
if use_count > 0 {
for _ in use_count..max_args {
uses.extend(quote! { Argument::None, });
}
let uses_name = format_ident!("uses_{}", opcode.ident());
defs_uses_functions.extend(quote! {
fn #uses_name(out: &mut Arguments, ins: Ins) { *out = [#uses]; }
});
uses_refs.extend(quote! { #uses_name, });
} else {
uses_refs.extend(quote! { defs_uses_empty, });
}
let defs_name = format_ident!("defs_{}", opcode.ident());
let uses_name = format_ident!("uses_{}", opcode.ident());
defs_uses_functions.extend(quote! {
const fn #defs_name(ins: &Ins) -> Arguments { [#defs] }
const fn #uses_name(ins: &Ins) -> Arguments { [#uses] }
});
defs_refs.extend(quote! { #defs_name, });
uses_refs.extend(quote! { #uses_name, });
}
defs_uses_functions.extend(quote! {
const fn defs_uses_illegal(_ins: &Ins) -> Arguments { [#none_args] }
fn defs_uses_empty(out: &mut Arguments, _ins: Ins) { *out = EMPTY_ARGS; }
});
// Filling the tables to 256 entries to avoid bounds checks
for _ in sorted_ops.len()..256 {
opcode_patterns.extend(quote! { (0, 0), });
opcode_names.extend(quote! { "<illegal>", });
base_functions_ref.extend(quote! { mnemonic_illegal, });
basic_functions_ref.extend(quote! { mnemonic_illegal, });
simplified_functions_ref.extend(quote! { mnemonic_illegal, });
defs_refs.extend(quote! { defs_uses_illegal, });
uses_refs.extend(quote! { defs_uses_illegal, });
defs_refs.extend(quote! { defs_uses_empty, });
uses_refs.extend(quote! { defs_uses_empty, });
}
let mut none_args = TokenStream::new();
for _ in 0..max_args {
none_args.extend(quote! { Argument::None, });
}
let max_args = Literal::usize_unsuffixed(max_args);
Ok(quote! {
#![allow(unused)]
#![cfg_attr(rustfmt, rustfmt_skip)]
#[comment = " Code generated by ppc750-genisa. DO NOT EDIT."]
use crate::disasm::*;
#[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)."]
const OPCODE_ENTRIES: [(u8, u8); 64] = [#opcode_entries];
static OPCODE_ENTRIES: [(u8, u8); 64] = [#opcode_entries];
#[doc = " The bitmask and pattern for each opcode."]
const OPCODE_PATTERNS: [(u32, u32); 256] = [#opcode_patterns];
static OPCODE_PATTERNS: [(u32, u32); 256] = [#opcode_patterns];
#[doc = " The name of each opcode."]
const OPCODE_NAMES: [&str; 256] = [#opcode_names];
static OPCODE_NAMES: [&str; 256] = [#opcode_names];
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
#[repr(u8)]
@ -314,21 +327,19 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
}
impl Opcode {
#[inline]
pub const fn _mnemonic(self) -> &'static str {
pub fn _mnemonic(self) -> &'static str {
OPCODE_NAMES[self as usize]
}
#[inline]
pub const fn _detect(code: u32) -> Self {
pub fn _detect(code: u32) -> Self {
let entry = OPCODE_ENTRIES[(code >> 26) as usize];
let mut i = entry.0;
while i < entry.1 {
for i in entry.0..entry.1 {
let pattern = OPCODE_PATTERNS[i as usize];
if (code & pattern.0) == pattern.1 {
#[comment = " Safety: The enum is repr(u8) and marked non_exhaustive"]
return unsafe { core::mem::transmute(i) };
}
i += 1;
}
Self::Illegal
}
@ -339,14 +350,33 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
}
pub type Arguments = [Argument; #max_args];
pub type MnemonicFunction = fn(&Ins) -> (&'static str, Arguments);
pub const EMPTY_ARGS: Arguments = [#none_args];
type MnemonicFunction = fn(&mut ParsedIns, Ins);
#mnemonic_functions
pub const BASE_MNEMONICS: [MnemonicFunction; 256] = [#base_functions_ref];
pub const SIMPLIFIED_MNEMONICS: [MnemonicFunction; 256] = [#simplified_functions_ref];
static BASIC_MNEMONICS: [MnemonicFunction; 256] = [#basic_functions_ref];
#[inline]
pub fn parse_basic(out: &mut ParsedIns, ins: Ins) {
BASIC_MNEMONICS[ins.op as usize](out, ins)
}
static SIMPLIFIED_MNEMONICS: [MnemonicFunction; 256] = [#simplified_functions_ref];
#[inline]
pub fn parse_simplified(out: &mut ParsedIns, ins: Ins) {
SIMPLIFIED_MNEMONICS[ins.op as usize](out, ins)
}
type DefsUsesFunction = fn(&mut Arguments, Ins);
#defs_uses_functions
pub type DefsUsesFunction = fn(&Ins) -> Arguments;
pub const DEFS_FUNCTIONS: [DefsUsesFunction; 256] = [#defs_refs];
pub const USES_FUNCTIONS: [DefsUsesFunction; 256] = [#uses_refs];
static DEFS_FUNCTIONS: [DefsUsesFunction; 256] = [#defs_refs];
#[inline]
pub fn parse_defs(out: &mut Arguments, ins: Ins) {
DEFS_FUNCTIONS[ins.op as usize](out, ins)
}
static USES_FUNCTIONS: [DefsUsesFunction; 256] = [#uses_refs];
#[inline]
pub fn parse_uses(out: &mut Arguments, ins: Ins) {
USES_FUNCTIONS[ins.op as usize](out, ins)
}
})
}
@ -388,17 +418,22 @@ fn gen_mnemonic(
max_args: usize,
replace: Option<&HashMap<String, String>>,
) -> Result<TokenStream> {
let mut arguments = TokenStream::new();
for field in args {
let arg = gen_argument(field, isa, replace.and_then(|m| m.get(field)))?;
arguments.extend(quote! { #arg, });
}
for _ in args.len()..max_args {
arguments.extend(quote! { Argument::None, });
}
let arguments = if args.is_empty() {
quote! { EMPTY_ARGS }
} else {
let mut inner = TokenStream::new();
for field in args {
let arg = gen_argument(field, isa, replace.and_then(|m| m.get(field)))?;
inner.extend(quote! { #arg, });
}
for _ in args.len()..max_args {
inner.extend(quote! { Argument::None, });
}
quote! { [#inner] }
};
if modifiers.is_empty() {
Ok(quote! { (#name, [#arguments]) })
Ok(quote! { ParsedIns { mnemonic: #name, args: #arguments } })
} else {
let names = modifier_names(name, modifiers, isa);
let mut bitset = quote! { 0 };
@ -411,6 +446,6 @@ fn gen_mnemonic(
bitset.extend(quote! { | (ins.#modifier() as usize) << #i });
}
}
Ok(quote! { ([#(#names),*][#bitset], [#arguments]) })
Ok(quote! { ParsedIns { mnemonic: [#(#names),*][#bitset], args: #arguments } })
}
}