From c954ad475550918f7d4365c1deeebaed113b6d6e Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 8 Jul 2020 20:26:54 -0700 Subject: [PATCH] refactor API and add support for more instructions --- src/instr_models.rs | 220 +++++---- src/lib.rs | 586 ++++++++++++++++++----- src/main.rs | 46 +- src/python.rs | 131 +++-- src/serde_hex.rs | 27 +- tests/test_power_instruction_analyzer.py | 159 +++--- 6 files changed, 847 insertions(+), 322 deletions(-) diff --git a/src/instr_models.rs b/src/instr_models.rs index 4b64aff..7ce6fef 100644 --- a/src/instr_models.rs +++ b/src/instr_models.rs @@ -1,8 +1,36 @@ -use crate::{DivInput, DivResult, OverflowFlags}; +use crate::{ConditionRegister, InstructionInput, InstructionResult, OverflowFlags}; -pub fn divdeo(inputs: DivInput) -> DivResult { - let dividend = i128::from(inputs.dividend as i64) << 64; - let divisor = i128::from(inputs.divisor as i64); +macro_rules! create_instr_variants { + ($fn:ident, $fno:ident, $fn_:ident, $fno_:ident, $iwidth:ident) => { + pub fn $fn(inputs: InstructionInput) -> InstructionResult { + InstructionResult { + overflow: None, + ..$fno(inputs) + } + } + pub fn $fn_(inputs: InstructionInput) -> InstructionResult { + let mut retval = $fno_(inputs); + let mut cr0 = retval.cr0.as_mut().expect("expected cr0 to be set"); + cr0.so = false; + retval.overflow = None; + retval + } + pub fn $fno_(inputs: InstructionInput) -> InstructionResult { + let mut retval = $fno(inputs); + let result = retval.rt.expect("expected rt to be set"); + let so = retval.overflow.expect("expected overflow to be set").so; + let cr0 = ConditionRegister::from_signed_int(result as $iwidth, so); + retval.cr0 = Some(cr0); + retval + } + }; +} + +create_instr_variants!(divde, divdeo, divde_, divdeo_, i64); + +pub fn divdeo(inputs: InstructionInput) -> InstructionResult { + let dividend = i128::from(inputs.ra as i64) << 64; + let divisor = i128::from(inputs.rb as i64); let overflow; let result; if divisor == 0 || (divisor == -1 && dividend == i128::min_value()) { @@ -18,18 +46,18 @@ pub fn divdeo(inputs: DivInput) -> DivResult { overflow = false; } } - DivResult { - result, - overflow: Some(OverflowFlags { - overflow, - overflow32: overflow, - }), + InstructionResult { + rt: Some(result), + overflow: Some(OverflowFlags::from_overflow(overflow)), + ..InstructionResult::default() } } -pub fn divdeuo(inputs: DivInput) -> DivResult { - let dividend = u128::from(inputs.dividend) << 64; - let divisor = u128::from(inputs.divisor); +create_instr_variants!(divdeu, divdeuo, divdeu_, divdeuo_, i64); + +pub fn divdeuo(inputs: InstructionInput) -> InstructionResult { + let dividend = u128::from(inputs.ra) << 64; + let divisor = u128::from(inputs.rb); let overflow; let result; if divisor == 0 { @@ -45,18 +73,18 @@ pub fn divdeuo(inputs: DivInput) -> DivResult { overflow = false; } } - DivResult { - result, - overflow: Some(OverflowFlags { - overflow, - overflow32: overflow, - }), + InstructionResult { + rt: Some(result), + overflow: Some(OverflowFlags::from_overflow(overflow)), + ..InstructionResult::default() } } -pub fn divdo(inputs: DivInput) -> DivResult { - let dividend = inputs.dividend as i64; - let divisor = inputs.divisor as i64; +create_instr_variants!(divd, divdo, divd_, divdo_, i64); + +pub fn divdo(inputs: InstructionInput) -> InstructionResult { + let dividend = inputs.ra as i64; + let divisor = inputs.rb as i64; let overflow; let result; if divisor == 0 || (divisor == -1 && dividend == i64::min_value()) { @@ -66,18 +94,18 @@ pub fn divdo(inputs: DivInput) -> DivResult { result = (dividend / divisor) as u64; overflow = false; } - DivResult { - result, - overflow: Some(OverflowFlags { - overflow, - overflow32: overflow, - }), + InstructionResult { + rt: Some(result), + overflow: Some(OverflowFlags::from_overflow(overflow)), + ..InstructionResult::default() } } -pub fn divduo(inputs: DivInput) -> DivResult { - let dividend: u64 = inputs.dividend; - let divisor: u64 = inputs.divisor; +create_instr_variants!(divdu, divduo, divdu_, divduo_, i64); + +pub fn divduo(inputs: InstructionInput) -> InstructionResult { + let dividend: u64 = inputs.ra; + let divisor: u64 = inputs.rb; let overflow; let result; if divisor == 0 { @@ -87,18 +115,18 @@ pub fn divduo(inputs: DivInput) -> DivResult { result = dividend / divisor; overflow = false; } - DivResult { - result, - overflow: Some(OverflowFlags { - overflow, - overflow32: overflow, - }), + InstructionResult { + rt: Some(result), + overflow: Some(OverflowFlags::from_overflow(overflow)), + ..InstructionResult::default() } } -pub fn divweo(inputs: DivInput) -> DivResult { - let dividend = i64::from(inputs.dividend as i32) << 32; - let divisor = i64::from(inputs.divisor as i32); +create_instr_variants!(divwe, divweo, divwe_, divweo_, i32); + +pub fn divweo(inputs: InstructionInput) -> InstructionResult { + let dividend = i64::from(inputs.ra as i32) << 32; + let divisor = i64::from(inputs.rb as i32); let overflow; let result; if divisor == 0 || (divisor == -1 && dividend == i64::min_value()) { @@ -114,18 +142,18 @@ pub fn divweo(inputs: DivInput) -> DivResult { overflow = false; } } - DivResult { - result, - overflow: Some(OverflowFlags { - overflow, - overflow32: overflow, - }), + InstructionResult { + rt: Some(result), + overflow: Some(OverflowFlags::from_overflow(overflow)), + ..InstructionResult::default() } } -pub fn divweuo(inputs: DivInput) -> DivResult { - let dividend = u64::from(inputs.dividend as u32) << 32; - let divisor = u64::from(inputs.divisor as u32); +create_instr_variants!(divweu, divweuo, divweu_, divweuo_, i32); + +pub fn divweuo(inputs: InstructionInput) -> InstructionResult { + let dividend = u64::from(inputs.ra as u32) << 32; + let divisor = u64::from(inputs.rb as u32); let overflow; let result; if divisor == 0 { @@ -141,18 +169,18 @@ pub fn divweuo(inputs: DivInput) -> DivResult { overflow = false; } } - DivResult { - result, - overflow: Some(OverflowFlags { - overflow, - overflow32: overflow, - }), + InstructionResult { + rt: Some(result), + overflow: Some(OverflowFlags::from_overflow(overflow)), + ..InstructionResult::default() } } -pub fn divwo(inputs: DivInput) -> DivResult { - let dividend = inputs.dividend as i32; - let divisor = inputs.divisor as i32; +create_instr_variants!(divw, divwo, divw_, divwo_, i32); + +pub fn divwo(inputs: InstructionInput) -> InstructionResult { + let dividend = inputs.ra as i32; + let divisor = inputs.rb as i32; let overflow; let result; if divisor == 0 || (divisor == -1 && dividend == i32::min_value()) { @@ -162,18 +190,18 @@ pub fn divwo(inputs: DivInput) -> DivResult { result = (dividend / divisor) as u32 as u64; overflow = false; } - DivResult { - result, - overflow: Some(OverflowFlags { - overflow, - overflow32: overflow, - }), + InstructionResult { + rt: Some(result), + overflow: Some(OverflowFlags::from_overflow(overflow)), + ..InstructionResult::default() } } -pub fn divwuo(inputs: DivInput) -> DivResult { - let dividend = inputs.dividend as u32; - let divisor = inputs.divisor as u32; +create_instr_variants!(divwu, divwuo, divwu_, divwuo_, i32); + +pub fn divwuo(inputs: InstructionInput) -> InstructionResult { + let dividend = inputs.ra as u32; + let divisor = inputs.rb as u32; let overflow; let result; if divisor == 0 { @@ -183,71 +211,69 @@ pub fn divwuo(inputs: DivInput) -> DivResult { result = (dividend / divisor) as u64; overflow = false; } - DivResult { - result, - overflow: Some(OverflowFlags { - overflow, - overflow32: overflow, - }), + InstructionResult { + rt: Some(result), + overflow: Some(OverflowFlags::from_overflow(overflow)), + ..InstructionResult::default() } } -pub fn modsd(inputs: DivInput) -> DivResult { - let dividend = inputs.dividend as i64; - let divisor = inputs.divisor as i64; +pub fn modsd(inputs: InstructionInput) -> InstructionResult { + let dividend = inputs.ra as i64; + let divisor = inputs.rb as i64; let result; if divisor == 0 || (divisor == -1 && dividend == i64::min_value()) { result = 0; } else { result = (dividend % divisor) as u64; } - DivResult { - result, - overflow: None, + InstructionResult { + rt: Some(result), + ..InstructionResult::default() } } -pub fn modud(inputs: DivInput) -> DivResult { - let dividend: u64 = inputs.dividend; - let divisor: u64 = inputs.divisor; +pub fn modud(inputs: InstructionInput) -> InstructionResult { + let dividend: u64 = inputs.ra; + let divisor: u64 = inputs.rb; let result; if divisor == 0 { result = 0; } else { result = dividend % divisor; } - DivResult { - result, - overflow: None, + InstructionResult { + rt: Some(result), + ..InstructionResult::default() } } -pub fn modsw(inputs: DivInput) -> DivResult { - let dividend = inputs.dividend as i32; - let divisor = inputs.divisor as i32; +pub fn modsw(inputs: InstructionInput) -> InstructionResult { + let dividend = inputs.ra as i32; + let divisor = inputs.rb as i32; let result; if divisor == 0 || (divisor == -1 && dividend == i32::min_value()) { result = 0; } else { result = (dividend % divisor) as u64; } - DivResult { - result, - overflow: None, + InstructionResult { + rt: Some(result), + ..InstructionResult::default() } } -pub fn moduw(inputs: DivInput) -> DivResult { - let dividend = inputs.dividend as u32; - let divisor = inputs.divisor as u32; +pub fn moduw(inputs: InstructionInput) -> InstructionResult { + let dividend = inputs.ra as u32; + let divisor = inputs.rb as u32; let result; if divisor == 0 { result = 0; } else { result = (dividend % divisor) as u64; } - DivResult { - result, - overflow: None, + InstructionResult { + rt: Some(result), + ..InstructionResult::default() } } diff --git a/src/lib.rs b/src/lib.rs index d6a8082..ec31e6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,42 +7,147 @@ compile_error!("native_instrs feature requires target_arch to be powerpc64"); pub mod instr_models; -mod python; mod serde_hex; use serde::{Deserialize, Serialize}; +use std::{ + cmp::Ordering, + ops::{Index, IndexMut}, +}; #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OverflowFlags { - pub overflow: bool, - pub overflow32: bool, + pub so: bool, + pub ov: bool, + pub ov32: bool, } impl OverflowFlags { - pub fn from_xer(xer: u64) -> Self { + pub const fn from_xer(xer: u64) -> Self { Self { - overflow: (xer & 0x4000_0000) != 0, - overflow32: (xer & 0x8_0000) != 0, + so: (xer & 0x8000_0000) != 0, + ov: (xer & 0x4000_0000) != 0, + ov32: (xer & 0x8_0000) != 0, + } + } + pub const fn from_overflow(overflow: bool) -> Self { + Self { + so: overflow, + ov: overflow, + ov32: overflow, } } } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct DivResult { - #[serde(with = "serde_hex::SerdeHex")] - pub result: u64, +pub struct ConditionRegister { + pub lt: bool, + pub gt: bool, + pub eq: bool, + pub so: bool, +} + +impl ConditionRegister { + pub const fn from_4_bits(bits: u8) -> Self { + // assert bits is 4-bits long + // can switch to using assert! once rustc feature const_panic is stabilized + [0; 0x10][bits as usize]; + + Self { + lt: (bits & 8) != 0, + gt: (bits & 4) != 0, + eq: (bits & 2) != 0, + so: (bits & 1) != 0, + } + } + pub const CR_FIELD_COUNT: usize = 8; + pub const fn from_cr_field(cr: u32, field_index: usize) -> Self { + // assert field_index is less than CR_FIELD_COUNT + // can switch to using assert! once rustc feature const_panic is stabilized + [0; Self::CR_FIELD_COUNT][field_index]; + + let reversed_field_index = Self::CR_FIELD_COUNT - field_index - 1; + let bits = (cr >> (4 * reversed_field_index)) & 0xF; + Self::from_4_bits(bits as u8) + } + pub fn from_signed_int(value: T, so: bool) -> Self { + let ordering = value.cmp(&T::default()); + Self { + lt: ordering == Ordering::Less, + gt: ordering == Ordering::Greater, + eq: ordering == Ordering::Equal, + so, + } + } +} + +#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct InstructionResult { + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "serde_hex::SerdeHex" + )] + pub rt: Option, #[serde(default, flatten, skip_serializing_if = "Option::is_none")] pub overflow: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cr0: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cr1: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cr2: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cr3: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cr4: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cr5: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cr6: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cr7: Option, +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)] +pub enum InstructionInputRegister { + #[serde(rename = "ra")] + Ra, + #[serde(rename = "rb")] + Rb, + #[serde(rename = "rc")] + Rc, } #[derive(Copy, Clone, Debug, Serialize, Deserialize)] -pub struct DivInput { +pub struct InstructionInput { + #[serde(with = "serde_hex::SerdeHex")] + pub ra: u64, #[serde(with = "serde_hex::SerdeHex")] - pub dividend: u64, + pub rb: u64, #[serde(with = "serde_hex::SerdeHex")] - pub divisor: u64, - #[serde(default, with = "serde_hex::SerdeHex")] - pub result_prev: u64, + pub rc: u64, +} + +impl Index for InstructionInput { + type Output = u64; + fn index(&self, index: InstructionInputRegister) -> &Self::Output { + match index { + InstructionInputRegister::Ra => &self.ra, + InstructionInputRegister::Rb => &self.rb, + InstructionInputRegister::Rc => &self.rc, + } + } +} + +impl IndexMut for InstructionInput { + fn index_mut(&mut self, index: InstructionInputRegister) -> &mut Self::Output { + match index { + InstructionInputRegister::Ra => &mut self.ra, + InstructionInputRegister::Rb => &mut self.rb, + InstructionInputRegister::Rc => &mut self.rc, + } + } } fn is_false(v: &bool) -> bool { @@ -50,13 +155,13 @@ fn is_false(v: &bool) -> bool { } #[derive(Copy, Clone, Debug, Serialize, Deserialize)] -pub struct TestDivCase { - pub instr: DivInstr, +pub struct TestCase { + pub instr: Instr, #[serde(flatten)] - pub inputs: DivInput, + pub inputs: InstructionInput, #[serde(default, skip_serializing_if = "Option::is_none")] - pub native_outputs: Option, - pub model_outputs: DivResult, + pub native_outputs: Option, + pub model_outputs: InstructionResult, #[serde(default, skip_serializing_if = "is_false")] pub model_mismatch: bool, } @@ -64,71 +169,221 @@ pub struct TestDivCase { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct WholeTest { #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub test_div_cases: Vec, + pub test_cases: Vec, pub any_model_mismatch: bool, } -macro_rules! make_div_functions { +#[cfg(feature = "native_instrs")] +macro_rules! map_instr_asm_args { + ([], [], []) => { + "" + }; + ([], [], [$string0:literal $($strings:literal)*]) => { + concat!(" ", $string0, $(", ", $strings),*) + }; + ([$($args:ident)*], [rt $($results:ident)*], [$($strings:literal)*]) => { + map_instr_asm_args!([$($args)*], [$($results)*], ["$0" $($strings)*]) + }; + ([ra $($args:ident)*], [$($results:ident)*], [$($strings:literal)*]) => { + map_instr_asm_args!([$($args)*], [$($results)*], ["$3" $($strings)*]) + }; + ([rb $($args:ident)*], [$($results:ident)*], [$($strings:literal)*]) => { + map_instr_asm_args!([$($args)*], [$($results)*], ["$4" $($strings)*]) + }; + ([rc $($args:ident)*], [$($results:ident)*], [$($strings:literal)*]) => { + map_instr_asm_args!([$($args)*], [$($results)*], ["$5" $($strings)*]) + }; + ([$($args:ident)*], [ov $($results:ident)*], [$($strings:literal)*]) => { + map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)*]) + }; + ([$($args:ident)*], [cr0 $($results:ident)*], [$($strings:literal)*]) => { + map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)*]) + }; + ([$($args:ident)*], [cr1 $($results:ident)*], [$($strings:literal)*]) => { + map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)*]) + }; + ([$($args:ident)*], [cr2 $($results:ident)*], [$($strings:literal)*]) => { + map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)*]) + }; + ([$($args:ident)*], [cr3 $($results:ident)*], [$($strings:literal)*]) => { + map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)*]) + }; + ([$($args:ident)*], [cr4 $($results:ident)*], [$($strings:literal)*]) => { + map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)*]) + }; + ([$($args:ident)*], [cr5 $($results:ident)*], [$($strings:literal)*]) => { + map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)*]) + }; + ([$($args:ident)*], [cr6 $($results:ident)*], [$($strings:literal)*]) => { + map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)*]) + }; + ([$($args:ident)*], [cr7 $($results:ident)*], [$($strings:literal)*]) => { + map_instr_asm_args!([$($args)*], [$($results)*], [$($strings)*]) + }; +} + +macro_rules! map_instr_input_registers { + ([], [$($reg:expr,)*]) => { + [$($reg,)*] + }; + ([ra $($args:ident)*], [$($reg:expr,)*]) => { + map_instr_input_registers!([$($args)*], [InstructionInputRegister::Ra, $($reg,)*]) + }; + ([rb $($args:ident)*], [$($reg:expr,)*]) => { + map_instr_input_registers!([$($args)*], [InstructionInputRegister::Rb, $($reg,)*]) + }; + ([rc $($args:ident)*], [$($reg:expr,)*]) => { + map_instr_input_registers!([$($args)*], [InstructionInputRegister::Rc, $($reg,)*]) + }; +} + +#[cfg(feature = "native_instrs")] +macro_rules! map_instr_results { + ($rt:ident, $xer:ident, $cr:ident, $retval:ident, []) => {}; + ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [rt $($args:ident)*]) => { + $retval.rt = Some($rt); + map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]); + }; + ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [ov $($args:ident)*]) => { + $retval.overflow = Some(OverflowFlags::from_xer($xer)); + map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]); + }; + ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [cr0 $($args:ident)*]) => { + $retval.cr0 = Some(ConditionRegister::from_cr_field($cr, 0)); + map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]); + }; + ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [cr1 $($args:ident)*]) => { + $retval.cr1 = Some(ConditionRegister::from_cr_field($cr, 1)); + map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]); + }; + ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [cr2 $($args:ident)*]) => { + $retval.cr2 = Some(ConditionRegister::from_cr_field($cr, 2)); + map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]); + }; + ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [cr3 $($args:ident)*]) => { + $retval.cr3 = Some(ConditionRegister::from_cr_field($cr, 3)); + map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]); + }; + ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [cr4 $($args:ident)*]) => { + $retval.cr4 = Some(ConditionRegister::from_cr_field($cr, 4)); + map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]); + }; + ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [cr5 $($args:ident)*]) => { + $retval.cr5 = Some(ConditionRegister::from_cr_field($cr, 5)); + map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]); + }; + ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [cr6 $($args:ident)*]) => { + $retval.cr6 = Some(ConditionRegister::from_cr_field($cr, 6)); + map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]); + }; + ($rt:ident, $xer:ident, $cr:ident, $retval:ident, [cr7 $($args:ident)*]) => { + $retval.cr7 = Some(ConditionRegister::from_cr_field($cr, 7)); + map_instr_results!($rt, $xer, $cr, $retval, [$($args)*]); + }; +} + +#[cfg(feature = "native_instrs")] +macro_rules! instr { ( - #[div] - { - $($div_enum:ident = $div_fn:ident ($div_instr:literal),)+ + #[enumerant = $enumerant:ident] + fn $fn:ident($($args:ident),*) -> ($($results:ident),*) { + $instr:literal } - #[rem] - { - $($rem_enum:ident = $rem_fn:ident ($rem_instr:literal),)+ + ) => { + pub fn $fn(inputs: InstructionInput) -> InstructionResult { + #![allow(unused_variables, unused_assignments)] + let InstructionInput { + ra, + rb, + rc, + } = inputs; + let rt: u64; + let xer: u64; + let cr: u32; + unsafe { + llvm_asm!( + concat!( + "mfxer $1\n", + "and $1, $1, $7\n", + "mtxer $1\n", + $instr, " ", + map_instr_asm_args!([$($args)*], [$($results)*], []), + "\n", + "mfxer $1\n", + "mfcr $2\n", + ) + : "=&r"(rt), "=&r"(xer), "=&r"(cr) + : "r"(ra), "r"(rb), "r"(rc), "r"(0u64), "r"(!0x8000_0000u64) + : "xer", "cr"); + } + let mut retval = InstructionResult::default(); + map_instr_results!(rt, xer, cr, retval, [$($results)*]); + retval } + }; +} + +macro_rules! instrs { + ( + $( + #[enumerant = $enumerant:ident] + fn $fn:ident($($args:ident),*) -> ($($results:ident),*) { + $instr:literal + } + )+ ) => { + #[cfg(feature = "python")] + macro_rules! wrap_all_instr_fns { + ($m:ident) => { + wrap_instr_fns! { + #![pymodule($m)] + + $(fn $fn(inputs: InstructionInput) -> InstructionResult;)* + } + }; + } + #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)] - pub enum DivInstr { + pub enum Instr { $( - #[serde(rename = $div_instr)] - $div_enum, - )+ - $( - #[serde(rename = $rem_instr)] - $rem_enum, + #[serde(rename = $instr)] + $enumerant, )+ } - impl DivInstr { + impl Instr { #[cfg(feature = "native_instrs")] - pub fn get_native_fn(self) -> fn(DivInput) -> DivResult { + pub fn get_native_fn(self) -> fn(InstructionInput) -> InstructionResult { match self { $( - Self::$div_enum => native_instrs::$div_fn, - )+ - $( - Self::$rem_enum => native_instrs::$rem_fn, + Self::$enumerant => native_instrs::$fn, )+ } } - pub fn get_model_fn(self) -> fn(DivInput) -> DivResult { + pub fn get_model_fn(self) -> fn(InstructionInput) -> InstructionResult { match self { $( - Self::$div_enum => instr_models::$div_fn, + Self::$enumerant => instr_models::$fn, )+ + } + } + pub fn get_used_input_registers(self) -> &'static [InstructionInputRegister] { + match self { $( - Self::$rem_enum => instr_models::$rem_fn, + Self::$enumerant => &map_instr_input_registers!([$($args)*], []), )+ } } pub fn name(self) -> &'static str { match self { $( - Self::$div_enum => $div_instr, - )+ - $( - Self::$rem_enum => $rem_instr, + Self::$enumerant => $instr, )+ } } pub const VALUES: &'static [Self] = &[ $( - Self::$div_enum, - )+ - $( - Self::$rem_enum, + Self::$enumerant, )+ ]; } @@ -138,51 +393,10 @@ macro_rules! make_div_functions { use super::*; $( - pub fn $div_fn(inputs: DivInput) -> DivResult { - let DivInput { - dividend, - divisor, - result_prev, - } = inputs; - let result: u64; - let xer: u64; - unsafe { - llvm_asm!( - concat!( - $div_instr, - " $0, $3, $4\n", - "mfxer $1" - ) - : "=&r"(result), "=&r"(xer) - : "0"(result_prev), "r"(dividend), "r"(divisor) - : "xer"); - } - DivResult { - result, - overflow: Some(OverflowFlags::from_xer(xer)), - } - } - )+ - $( - pub fn $rem_fn(inputs: DivInput) -> DivResult { - let DivInput { - dividend, - divisor, - result_prev, - } = inputs; - let result: u64; - unsafe { - llvm_asm!( - concat!( - $rem_instr, - " $0, $2, $3" - ) - : "=&r"(result) - : "0"(result_prev), "r"(dividend), "r"(divisor)); - } - DivResult { - result, - overflow: None, + instr! { + #[enumerant = $enumerant] + fn $fn($($args),*) -> ($($results),*) { + $instr } } )+ @@ -190,23 +404,169 @@ macro_rules! make_div_functions { }; } -make_div_functions! { - #[div] - { - DivDEO = divdeo("divdeo"), - DivDEUO = divdeuo("divdeuo"), - DivDO = divdo("divdo"), - DivDUO = divduo("divduo"), - DivWEO = divweo("divweo"), - DivWEUO = divweuo("divweuo"), - DivWO = divwo("divwo"), - DivWUO = divwuo("divwuo"), +instrs! { + // divde + #[enumerant = DivDE] + fn divde(ra, rb) -> (rt) { + "divde" + } + #[enumerant = DivDEO] + fn divdeo(ra, rb) -> (rt, ov) { + "divdeo" + } + #[enumerant = DivDE_] + fn divde_(ra, rb) -> (rt, cr0) { + "divde." + } + #[enumerant = DivDEO_] + fn divdeo_(ra, rb) -> (rt, ov, cr0) { + "divdeo." + } + + // divdeu + #[enumerant = DivDEU] + fn divdeu(ra, rb) -> (rt) { + "divdeu" + } + #[enumerant = DivDEUO] + fn divdeuo(ra, rb) -> (rt, ov) { + "divdeuo" + } + #[enumerant = DivDEU_] + fn divdeu_(ra, rb) -> (rt, cr0) { + "divdeu." + } + #[enumerant = DivDEUO_] + fn divdeuo_(ra, rb) -> (rt, ov, cr0) { + "divdeuo." + } + + // divd + #[enumerant = DivD] + fn divd(ra, rb) -> (rt) { + "divd" + } + #[enumerant = DivDO] + fn divdo(ra, rb) -> (rt, ov) { + "divdo" + } + #[enumerant = DivD_] + fn divd_(ra, rb) -> (rt, cr0) { + "divd." + } + #[enumerant = DivDO_] + fn divdo_(ra, rb) -> (rt, ov, cr0) { + "divdo." + } + + // divdu + #[enumerant = DivDU] + fn divdu(ra, rb) -> (rt) { + "divdu" + } + #[enumerant = DivDUO] + fn divduo(ra, rb) -> (rt, ov) { + "divduo" + } + #[enumerant = DivDU_] + fn divdu_(ra, rb) -> (rt, cr0) { + "divdu." + } + #[enumerant = DivDUO_] + fn divduo_(ra, rb) -> (rt, ov, cr0) { + "divduo." + } + + // divwe + #[enumerant = DivWE] + fn divwe(ra, rb) -> (rt) { + "divwe" + } + #[enumerant = DivWEO] + fn divweo(ra, rb) -> (rt, ov) { + "divweo" + } + #[enumerant = DivWE_] + fn divwe_(ra, rb) -> (rt, cr0) { + "divwe." + } + #[enumerant = DivWEO_] + fn divweo_(ra, rb) -> (rt, ov, cr0) { + "divweo." + } + + // divweu + #[enumerant = DivWEU] + fn divweu(ra, rb) -> (rt) { + "divweu" + } + #[enumerant = DivWEUO] + fn divweuo(ra, rb) -> (rt, ov) { + "divweuo" + } + #[enumerant = DivWEU_] + fn divweu_(ra, rb) -> (rt, cr0) { + "divweu." + } + #[enumerant = DivWEUO_] + fn divweuo_(ra, rb) -> (rt, ov, cr0) { + "divweuo." + } + + // divw + #[enumerant = DivW] + fn divw(ra, rb) -> (rt) { + "divw" + } + #[enumerant = DivWO] + fn divwo(ra, rb) -> (rt, ov) { + "divwo" + } + #[enumerant = DivW_] + fn divw_(ra, rb) -> (rt, cr0) { + "divw." + } + #[enumerant = DivWO_] + fn divwo_(ra, rb) -> (rt, ov, cr0) { + "divwo." + } + + // divwu + #[enumerant = DivWU] + fn divwu(ra, rb) -> (rt) { + "divwu" } - #[rem] - { - ModSD = modsd("modsd"), - ModUD = modud("modud"), - ModSW = modsw("modsw"), - ModUW = moduw("moduw"), + #[enumerant = DivWUO] + fn divwuo(ra, rb) -> (rt, ov) { + "divwuo" + } + #[enumerant = DivWU_] + fn divwu_(ra, rb) -> (rt, cr0) { + "divwu." + } + #[enumerant = DivWUO_] + fn divwuo_(ra, rb) -> (rt, ov, cr0) { + "divwuo." + } + + // mod* + #[enumerant = ModSD] + fn modsd(ra, rb) -> (rt) { + "modsd" + } + #[enumerant = ModUD] + fn modud(ra, rb) -> (rt) { + "modud" + } + #[enumerant = ModSW] + fn modsw(ra, rb) -> (rt) { + "modsw" + } + #[enumerant = ModUW] + fn moduw(ra, rb) -> (rt) { + "moduw" } } + +// must be after instrs macro call since it uses a macro definition +mod python; diff --git a/src/main.rs b/src/main.rs index cc16cf0..63044c8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,9 @@ // SPDX-License-Identifier: LGPL-2.1-or-later // See Notices.txt for copyright information -use power_instruction_analyzer::{DivInput, DivInstr, TestDivCase, WholeTest}; +use power_instruction_analyzer::{ + Instr, InstructionInput, InstructionInputRegister, TestCase, WholeTest, +}; const TEST_VALUES: &[u64] = &[ 0x0, @@ -17,17 +19,33 @@ const TEST_VALUES: &[u64] = &[ 0x1234_5678_7FFF_FFFF, ]; +fn call_with_inputs( + mut inputs: InstructionInput, + input_registers: &[InstructionInputRegister], + f: &mut impl FnMut(InstructionInput), +) { + if let Some((&input_register, input_registers)) = input_registers.split_first() { + for &i in TEST_VALUES { + inputs[input_register] = i; + call_with_inputs(inputs, input_registers, f); + } + } else { + f(inputs); + } +} + fn main() { - let mut test_div_cases = Vec::new(); + let mut test_cases = Vec::new(); let mut any_model_mismatch = false; - for &instr in DivInstr::VALUES { - for ÷nd in TEST_VALUES { - for &divisor in TEST_VALUES { - let inputs = DivInput { - dividend, - divisor, - result_prev: 0xFECD_BA98_7654_3210, - }; + for &instr in Instr::VALUES { + call_with_inputs( + InstructionInput { + ra: 0, + rb: 0, + rc: 0, + }, + instr.get_used_input_registers(), + &mut |inputs| { let model_outputs = instr.get_model_fn()(inputs); #[cfg(feature = "native_instrs")] let native_outputs = Some(instr.get_native_fn()(inputs)); @@ -38,18 +56,18 @@ fn main() { _ => false, }; any_model_mismatch |= model_mismatch; - test_div_cases.push(TestDivCase { + test_cases.push(TestCase { instr, inputs, native_outputs, model_outputs, model_mismatch, }); - } - } + }, + ); } let whole_test = WholeTest { - test_div_cases, + test_cases, any_model_mismatch, }; serde_json::to_writer_pretty(std::io::stdout().lock(), &whole_test).unwrap(); diff --git a/src/python.rs b/src/python.rs index 220663e..a1f6071 100644 --- a/src/python.rs +++ b/src/python.rs @@ -3,7 +3,7 @@ #![cfg(feature = "python")] -use crate::{DivInput, DivResult, OverflowFlags}; +use crate::{ConditionRegister, Instr, InstructionInput, InstructionResult, OverflowFlags}; use pyo3::{prelude::*, wrap_pyfunction, PyObjectProtocol}; use std::{borrow::Cow, cell::RefCell, fmt}; @@ -123,6 +123,7 @@ macro_rules! wrap_type { // use tt to work around PyO3 bug fixed in PyO3#832 #[pyclass $($pyclass_args:tt)?] #[wrapped($value:ident: $wrapped:ident)] + #[args $new_args:tt] $(#[$meta:meta])* struct $wrapper:ident { $( @@ -154,6 +155,7 @@ macro_rules! wrap_type { #[pymethods] impl $wrapper { #[new] + #[args $new_args] fn new($($field_name:$field_type),*) -> Self { Self { $value: $wrapped { @@ -234,57 +236,112 @@ fn power_instruction_analyzer(_py: Python, m: &PyModule) -> PyResult<()> { #[pymodule(m)] #[pyclass(name = OverflowFlags)] #[wrapped(value: OverflowFlags)] - #[text_signature = "(overflow, overflow32)"] + #[args(so, ov, ov32)] + #[text_signature = "(so, ov, ov32)"] struct PyOverflowFlags { - #[set = set_overflow] - overflow: bool, - #[set = set_overflow32] - overflow32: bool, + #[set = set_so] + so: bool, + #[set = set_ov] + ov: bool, + #[set = set_ov32] + ov32: bool, + } + } + + wrap_type! { + #[pymodule(m)] + #[pyclass(name = ConditionRegister)] + #[wrapped(value: ConditionRegister)] + #[args(lt, gt, eq, so)] + #[text_signature = "(lt, gt, eq, so)"] + struct PyConditionRegister { + #[set = set_lt] + lt: bool, + #[set = set_gt] + gt: bool, + #[set = set_eq] + eq: bool, + #[set = set_so] + so: bool, } } wrap_type! { #[pymodule(m)] - #[pyclass(name = DivInput)] - #[wrapped(value: DivInput)] - #[text_signature = "(dividend, divisor, result_prev)"] - struct PyDivInput { - #[set = set_dividend] - dividend: u64, - #[set = set_divisor] - divisor: u64, - #[set = set_result_prev] - result_prev: u64, + #[pyclass(name = InstructionInput)] + #[wrapped(value: InstructionInput)] + #[args(ra, rb, rc)] + #[text_signature = "(ra, rb, rc)"] + struct PyInstructionInput { + #[set = set_ra] + ra: u64, + #[set = set_rb] + rb: u64, + #[set = set_rc] + rc: u64, } } wrap_type! { #[pymodule(m)] - #[pyclass(name = DivResult)] - #[wrapped(value: DivResult)] - #[text_signature = "(result, overflow)"] - struct PyDivResult { - #[set = set_result] - result: u64, + #[pyclass(name = InstructionResult)] + #[wrapped(value: InstructionResult)] + #[args( + rt="None", + overflow="None", + cr0="None", + cr1="None", + cr2="None", + cr3="None", + cr4="None", + cr5="None", + cr6="None", + cr7="None" + )] + #[text_signature = "(\ + rt=None, \ + overflow=None, \ + cr0=None, \ + cr1=None, \ + cr2=None, \ + cr3=None, \ + cr4=None, \ + cr5=None, \ + cr6=None, \ + cr7=None)" + ] + struct PyInstructionResult { + #[set = set_rt] + rt: Option, #[set = set_overflow] overflow: Option, + #[set = set_cr0] + cr0: Option, + #[set = set_cr1] + cr1: Option, + #[set = set_cr2] + cr2: Option, + #[set = set_cr3] + cr3: Option, + #[set = set_cr4] + cr4: Option, + #[set = set_cr5] + cr5: Option, + #[set = set_cr6] + cr6: Option, + #[set = set_cr7] + cr7: Option, } } - wrap_instr_fns! { - #![pymodule(m)] - fn divdeo(inputs: DivInput) -> DivResult; - fn divdeuo(inputs: DivInput) -> DivResult; - fn divdo(inputs: DivInput) -> DivResult; - fn divduo(inputs: DivInput) -> DivResult; - fn divweo(inputs: DivInput) -> DivResult; - fn divweuo(inputs: DivInput) -> DivResult; - fn divwo(inputs: DivInput) -> DivResult; - fn divwuo(inputs: DivInput) -> DivResult; - fn modsd(inputs: DivInput) -> DivResult; - fn modud(inputs: DivInput) -> DivResult; - fn modsw(inputs: DivInput) -> DivResult; - fn moduw(inputs: DivInput) -> DivResult; - } + m.setattr( + "INSTRS", + Instr::VALUES + .iter() + .map(|&instr| instr.name()) + .collect::>(), + )?; + + wrap_all_instr_fns!(m); Ok(()) } diff --git a/src/serde_hex.rs b/src/serde_hex.rs index 17ec8a8..be362c8 100644 --- a/src/serde_hex.rs +++ b/src/serde_hex.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later // See Notices.txt for copyright information -use serde::{de, Deserialize, Deserializer, Serializer}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; pub(crate) trait SerdeHex { fn serialize(&self, serializer: S) -> Result; @@ -10,6 +10,31 @@ pub(crate) trait SerdeHex { Self: Sized; } +#[derive(Deserialize, Serialize)] +struct SerdeHexWrapper(#[serde(with = "SerdeHex")] T); + +fn serialize_ref_helper( + v: &&T, + serializer: S, +) -> Result { + v.serialize(serializer) +} + +#[derive(Serialize)] +struct SerdeHexRefWrapper<'a, T: SerdeHex>(#[serde(serialize_with = "serialize_ref_helper")] &'a T); + +impl SerdeHex for Option { + fn serialize(&self, serializer: S) -> Result { + self.as_ref().map(SerdeHexRefWrapper).serialize(serializer) + } + fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result + where + Self: Sized, + { + Ok(Option::>::deserialize(deserializer)?.map(|v| v.0)) + } +} + macro_rules! impl_hex_for_uint { ($ty:ty) => { impl SerdeHex for $ty { diff --git a/tests/test_power_instruction_analyzer.py b/tests/test_power_instruction_analyzer.py index da954a0..3fb483f 100644 --- a/tests/test_power_instruction_analyzer.py +++ b/tests/test_power_instruction_analyzer.py @@ -8,99 +8,138 @@ import power_instruction_analyzer as pia class TestOverflowFlags(unittest.TestCase): def test_text_signature(self): self.assertEqual(pia.OverflowFlags.__text_signature__, - "(overflow, overflow32)") + "(so, ov, ov32)") def test_fields(self): - v = pia.OverflowFlags(overflow=False, overflow32=True) - self.assertEqual(v.overflow, False) - self.assertEqual(v.overflow32, True) - v.overflow = True - self.assertEqual(v.overflow, True) - v.overflow32 = False - self.assertEqual(v.overflow32, False) + v = pia.OverflowFlags(so=False, ov=False, ov32=True) + self.assertEqual(v.so, False) + self.assertEqual(v.ov, False) + self.assertEqual(v.ov32, True) + v.so = True + self.assertEqual(v.so, True) + v.ov = True + self.assertEqual(v.ov, True) + v.ov32 = False + self.assertEqual(v.ov32, False) def test_str_repr(self): - v = pia.OverflowFlags(overflow=False, overflow32=True) + v = pia.OverflowFlags(so=False, ov=False, ov32=True) self.assertEqual(str(v), - '{"overflow":false,"overflow32":true}') + '{"so":false,"ov":false,"ov32":true}') self.assertEqual(repr(v), - "OverflowFlags(overflow=False, overflow32=True)") + "OverflowFlags(so=False, ov=False, ov32=True)") -class TestDivInput(unittest.TestCase): +class TestConditionRegister(unittest.TestCase): def test_text_signature(self): - self.assertEqual(pia.DivInput.__text_signature__, - "(dividend, divisor, result_prev)") + self.assertEqual(pia.ConditionRegister.__text_signature__, + "(lt, gt, eq, so)") def test_fields(self): - v = pia.DivInput(dividend=123, divisor=456, result_prev=789) - self.assertEqual(v.dividend, 123) - self.assertEqual(v.divisor, 456) - self.assertEqual(v.result_prev, 789) - v.dividend = 1234 - self.assertEqual(v.dividend, 1234) - v.divisor = 4567 - self.assertEqual(v.divisor, 4567) - v.result_prev = 7890 - self.assertEqual(v.result_prev, 7890) + v = pia.ConditionRegister(lt=False, gt=True, eq=False, so=True) + self.assertEqual(v.lt, False) + self.assertEqual(v.gt, True) + self.assertEqual(v.eq, False) + self.assertEqual(v.so, True) + v.lt = True + self.assertEqual(v.lt, True) + v.gt = False + self.assertEqual(v.gt, False) + v.eq = True + self.assertEqual(v.eq, True) + v.so = False + self.assertEqual(v.so, False) def test_str_repr(self): - v = pia.DivInput(dividend=123, divisor=456, result_prev=789) + v = pia.ConditionRegister(lt=False, gt=True, eq=False, so=True) self.assertEqual(str(v), - '{"dividend":"0x7B","divisor":"0x1C8","result_prev":"0x315"}') + '{"lt":false,"gt":true,"eq":false,"so":true}') self.assertEqual(repr(v), - "DivInput(dividend=123, divisor=456, result_prev=789)") + "ConditionRegister(lt=False, gt=True, eq=False, so=True)") -class TestDivResult(unittest.TestCase): +class TestInstructionInput(unittest.TestCase): def test_text_signature(self): - self.assertEqual(pia.DivResult.__text_signature__, - "(result, overflow)") + self.assertEqual(pia.InstructionInput.__text_signature__, + "(ra, rb, rc)") def test_fields(self): - v = pia.DivResult(result=1234, - overflow=pia.OverflowFlags(overflow=False, overflow32=True)) - self.assertEqual(v.result, 1234) + v = pia.InstructionInput(ra=123, rb=456, rc=789) + self.assertEqual(v.ra, 123) + self.assertEqual(v.rb, 456) + self.assertEqual(v.rc, 789) + v.ra = 1234 + self.assertEqual(v.ra, 1234) + v.rb = 4567 + self.assertEqual(v.rb, 4567) + v.rc = 7890 + self.assertEqual(v.rc, 7890) + + def test_str_repr(self): + v = pia.InstructionInput(ra=123, rb=456, rc=789) + self.assertEqual(str(v), + '{"ra":"0x7B","rb":"0x1C8","rc":"0x315"}') + self.assertEqual(repr(v), + "InstructionInput(ra=123, rb=456, rc=789)") + + +class TestInstructionResult(unittest.TestCase): + def test_text_signature(self): + self.assertEqual(pia.InstructionResult.__text_signature__, + "(rt=None, overflow=None, cr0=None, cr1=None, " + + "cr2=None, cr3=None, cr4=None, cr5=None, cr6=None, cr7=None)") + + def test_fields(self): + v = pia.InstructionResult( + overflow=pia.OverflowFlags(so=False, ov=False, ov32=True)) + self.assertIsNone(v.rt) self.assertIsNotNone(v.overflow) - self.assertEqual(v.overflow.overflow, False) - self.assertEqual(v.overflow.overflow32, True) - v.result = 123 - self.assertEqual(v.result, 123) + self.assertEqual(v.overflow.so, False) + self.assertEqual(v.overflow.ov, False) + self.assertEqual(v.overflow.ov32, True) + self.assertIsNone(v.cr0) + self.assertIsNone(v.cr1) + self.assertIsNone(v.cr2) + self.assertIsNone(v.cr3) + self.assertIsNone(v.cr4) + self.assertIsNone(v.cr5) + self.assertIsNone(v.cr6) + self.assertIsNone(v.cr7) + v.rt = 123 + self.assertEqual(v.rt, 123) v.overflow = None self.assertIsNone(v.overflow) + v.cr2 = pia.ConditionRegister(lt=False, gt=False, eq=False, so=False) + self.assertIsNotNone(v.cr2) def test_str_repr(self): - v = pia.DivResult(result=1234, - overflow=pia.OverflowFlags(overflow=False, overflow32=True)) + v = pia.InstructionResult( + overflow=pia.OverflowFlags(so=False, ov=False, ov32=True), + cr0=pia.ConditionRegister(lt=True, gt=True, eq=True, so=True), + cr2=pia.ConditionRegister(lt=False, gt=False, eq=False, so=False)) self.assertEqual(str(v), - '{"result":"0x4D2","overflow":false,"overflow32":true}') + '{"so":false,"ov":false,"ov32":true,' + + '"cr0":{"lt":true,"gt":true,"eq":true,"so":true},' + + '"cr2":{"lt":false,"gt":false,"eq":false,"so":false}}') self.assertEqual(repr(v), - "DivResult(result=1234, overflow=OverflowFlags(overflow=False, overflow32=True))") + "InstructionResult(rt=None, " + + "overflow=OverflowFlags(so=False, ov=False, ov32=True), " + + "cr0=ConditionRegister(lt=True, gt=True, eq=True, so=True), " + + "cr1=None, " + + "cr2=ConditionRegister(lt=False, gt=False, eq=False, so=False), " + + "cr3=None, cr4=None, cr5=None, cr6=None, cr7=None)") class TestDivInstrs(unittest.TestCase): - cases = [ - ("divdeo", '{"result":"0x0","overflow":true,"overflow32":true}'), - ("divdeuo", '{"result":"0x0","overflow":true,"overflow32":true}'), - ("divdo", '{"result":"0x36","overflow":false,"overflow32":false}'), - ("divduo", '{"result":"0x36","overflow":false,"overflow32":false}'), - ("divweo", '{"result":"0x0","overflow":true,"overflow32":true}'), - ("divweuo", '{"result":"0x0","overflow":true,"overflow32":true}'), - ("divwo", '{"result":"0x36","overflow":false,"overflow32":false}'), - ("divwuo", '{"result":"0x36","overflow":false,"overflow32":false}'), - ("modsd", '{"result":"0x10"}'), - ("modud", '{"result":"0x10"}'), - ("modsw", '{"result":"0x10"}'), - ("moduw", '{"result":"0x10"}'), - ] - def test(self): - v = pia.DivInput(dividend=0x1234, divisor=0x56, result_prev=0x789) - for fn_name, expected in self.cases: - with self.subTest(fn_name=fn_name): + v = pia.InstructionInput(ra=0x1234, rb=0x56, rc=0x789) + for instr in pia.INSTRS: + with self.subTest(instr=instr): + fn_name = instr.replace(".", "_") fn = getattr(pia, fn_name) + self.assertEqual(fn.__text_signature__, "(inputs)") results = fn(v) - self.assertEqual(str(results), expected) + self.assertIsInstance(results, pia.InstructionResult) if __name__ == "__main__": -- 2.30.2