From: Jacob Lifshay Date: Sat, 29 Aug 2020 01:15:09 +0000 (-0700) Subject: working on adding proc-macro that replaces macro_rules-based instrs! macro X-Git-Tag: v0.2.0~32 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=d5a5f31497c7a7f9fdeaba6fc290557e45d8e2c0;p=power-instruction-analyzer.git working on adding proc-macro that replaces macro_rules-based instrs! macro --- diff --git a/Cargo.lock b/Cargo.lock index 604234d..964046b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -163,12 +163,22 @@ dependencies = [ name = "power-instruction-analyzer" version = "0.1.0" dependencies = [ + "power-instruction-analyzer-proc-macro", "pyo3", "serde", "serde_json", "serde_plain", ] +[[package]] +name = "power-instruction-analyzer-proc-macro" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro-hack" version = "0.5.18" diff --git a/Cargo.toml b/Cargo.toml index d7f17e6..36f3a4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,4 +20,13 @@ crate-type = ["rlib", "cdylib"] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_plain = "0.3" -pyo3 = { version = "0.11", optional = true } \ No newline at end of file +pyo3 = { version = "0.11", optional = true } + +[dependencies.power-instruction-analyzer-proc-macro] +path = "power-instruction-analyzer-proc-macro" + +[workspace] +members = [ + ".", + "power-instruction-analyzer-proc-macro", +] \ No newline at end of file diff --git a/power-instruction-analyzer-proc-macro/Cargo.toml b/power-instruction-analyzer-proc-macro/Cargo.toml new file mode 100644 index 0000000..3b72c25 --- /dev/null +++ b/power-instruction-analyzer-proc-macro/Cargo.toml @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# See Notices.txt for copyright information +[package] +name = "power-instruction-analyzer-proc-macro" +version = "0.1.0" +authors = ["Jacob Lifshay "] +edition = "2018" +license = "LGPL-2.1-or-later" + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0" +proc-macro2 = "1.0" +syn = { version = "1.0", features = ["full", "parsing"] } \ No newline at end of file diff --git a/power-instruction-analyzer-proc-macro/src/lib.rs b/power-instruction-analyzer-proc-macro/src/lib.rs new file mode 100644 index 0000000..deb5dd9 --- /dev/null +++ b/power-instruction-analyzer-proc-macro/src/lib.rs @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// See Notices.txt for copyright information + +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote, ToTokens, TokenStreamExt}; +use std::fmt; +use syn::{ + braced, bracketed, parenthesized, + parse::{Parse, ParseStream}, + parse_macro_input, + punctuated::Punctuated, + Attribute, Error, ItemFn, LitStr, Token, +}; + +macro_rules! valid_enumerants_as_string { + ($enumerant:ident) => { + concat!("`", stringify!($enumerant), "`") + }; + ($enumerant1:ident, $enumerant2:ident) => { + concat!("`", stringify!($enumerant1), "` and `", stringify!($enumerant2), "`") + }; + ($($enumerant:ident),+) => { + valid_enumerants_as_string!((), ($($enumerant),+)) + }; + (($first_enumerant:ident, $($enumerant:ident,)+), ($last_enumerant:ident)) => { + concat!( + "`", + stringify!($first_enumerant), + $( + "`, `", + stringify!($enumerant), + )+ + "`, and `", + stringify!($last_enumerant), + "`" + ) + }; + (($($enumerants:ident,)*), ($next_enumerant:ident, $($rest:ident),*)) => { + valid_enumerants_as_string!(($($enumerants,)* $next_enumerant,), ($($rest),*)) + }; + () => { + "" + }; +} + +macro_rules! ident_enum { + ( + #[parse_error_msg = $parse_error_msg:literal] + enum $enum_name:ident { + $( + $enumerant:ident, + )* + } + ) => { + #[derive(Copy, Clone, Eq, PartialEq, Hash)] + enum $enum_name { + $( + $enumerant(T), + )* + } + + impl fmt::Debug for $enum_name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.name()) + } + } + + impl $enum_name { + fn enumerant(&self) -> $enum_name<()> { + match self { + $( + $enum_name::$enumerant(_) => $enum_name::$enumerant(()), + )* + } + } + fn name(&self) -> &'static str { + match self { + $( + $enum_name::$enumerant(_) => stringify!($enumerant), + )* + } + } + } + + impl $enum_name { + fn into_ident(self) -> Ident { + match self { + $( + $enum_name::$enumerant(span) => Ident::new(stringify!($enumerant), span), + )* + } + } + } + + impl ToTokens for $enum_name { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append(self.clone().into_ident()); + } + } + + impl Parse for $enum_name { + fn parse(input: ParseStream) -> syn::Result { + let id: Ident = input.parse()?; + $( + if id == stringify!($enumerant) { + return Ok($enum_name::$enumerant(id.span())); + } + )* + Err(Error::new_spanned( + id, + concat!( + $parse_error_msg, + ": valid values are: ", + valid_enumerants_as_string!($($enumerant),*) + ) + )) + } + } + }; +} + +ident_enum! { + #[parse_error_msg = "unknown instruction input"] + enum InstructionInput { + Ra, + Rb, + Rc, + Carry, + } +} + +ident_enum! { + #[parse_error_msg = "unknown instruction output"] + enum InstructionOutput { + Rt, + Carry, + Overflow, + CR0, + } +} + +#[derive(Debug)] +struct Instruction { + enumerant: Ident, + fn_name: Ident, + inputs: Punctuated, + outputs: Punctuated, + instruction_name: LitStr, +} + +impl Parse for Instruction { + fn parse(input: ParseStream) -> syn::Result { + input.parse::()?; + let enumerant_attr_tokens; + bracketed!(enumerant_attr_tokens in input); + let enumerant_name: Ident = enumerant_attr_tokens.parse()?; + if enumerant_name != "enumerant" { + return Err(Error::new_spanned( + enumerant_name, + "expected `#[enumerant = ...]` attribute", + )); + } + enumerant_attr_tokens.parse::()?; + let enumerant: Ident = enumerant_attr_tokens.parse()?; + input.parse::()?; + let fn_name: Ident = input.parse()?; + let inputs_tokens; + parenthesized!(inputs_tokens in input); + let inputs = inputs_tokens.parse_terminated(InstructionInput::parse)?; + input.parse::)>()?; + let outputs_tokens; + parenthesized!(outputs_tokens in input); + let outputs = outputs_tokens.parse_terminated(InstructionOutput::parse)?; + let body_tokens; + braced!(body_tokens in input); + let instruction_name: LitStr = body_tokens.parse()?; + Ok(Self { + enumerant, + fn_name, + inputs, + outputs, + instruction_name, + }) + } +} + +impl Instruction { + fn map_input_registers(&self) -> syn::Result> { + todo!() + } + fn to_assembly_text(&self) -> syn::Result { + let mut retval = String::new(); + retval += "mfxer $1\n\ + and $1, $1, $7\n\ + mtxer $1\n"; + todo!("map_instr_asm_args!([$($args)*], [$($results)*], []),"); + retval += "\n\ + mfxer $1\n\ + mfcr $2\n"; + Ok(retval) + } + fn to_native_fn_tokens(&self) -> syn::Result { + let Instruction { + enumerant, + fn_name, + inputs, + outputs, + instruction_name, + } = self; + let assembly_text = self.to_assembly_text()?; + let mut handle_inputs = Vec::::new(); + unimplemented!("fill handle_inputs"); + let mut handle_outputs = Vec::::new(); + unimplemented!( + "fill handle_outputs\ + map_instr_results!(rt, xer, cr, retval, [$($results)*]);" + ); + Ok(quote! { + pub fn #fn_name(inputs: InstructionInput) -> InstructionResult { + #![allow(unused_variables, unused_assignments)] + let InstructionInput { + ra, + rb, + rc, + carry, + } = inputs; + let rt: u64; + let xer: u64; + let cr: u32; + #(#handle_inputs)* + unsafe { + llvm_asm!( + #assembly_text + : "=&b"(rt), "=&b"(xer), "=&b"(cr) + : "b"(ra), "b"(rb), "b"(rc), "b"(0u64), "b"(!0x8000_0000u64) + : "xer", "cr"); + } + let mut retval = InstructionOutput::default(); + #(#handle_outputs)* + retval + } + }) + } +} + +#[derive(Debug)] +struct Instructions { + instructions: Vec, +} + +impl Instructions { + fn to_tokens(&self) -> syn::Result { + let mut fn_names = Vec::new(); + let mut instr_enumerants = Vec::new(); + let mut get_native_fn_match_cases = Vec::new(); + let mut get_model_fn_match_cases = Vec::new(); + let mut get_used_input_registers_match_cases = Vec::new(); + let mut name_match_cases = Vec::new(); + let mut enumerants = Vec::new(); + let mut native_fn_tokens = Vec::new(); + for instruction in &self.instructions { + let Instruction { + enumerant, + fn_name, + inputs, + outputs, + instruction_name, + } = instruction; + fn_names.push(fn_name); + enumerants.push(enumerant); + instr_enumerants.push(quote! { + #[serde(rename = #instruction_name)] + #enumerant, + }); + get_native_fn_match_cases.push(quote! { + Self::#enumerant => native_instrs::#fn_name, + }); + get_model_fn_match_cases.push(quote! { + Self::#enumerant => instr_models::#fn_name, + }); + let mapped_input_registers = instruction.map_input_registers()?; + get_used_input_registers_match_cases.push(quote! { + Self::#enumerant => &[#(#mapped_input_registers),*], + }); + name_match_cases.push(quote! { + Self::#enumerant => #instruction_name, + }); + native_fn_tokens.push(instruction.to_native_fn_tokens()?); + } + Ok(quote! { + #[cfg(feature = "python")] + macro_rules! wrap_all_instr_fns { + ($m:ident) => { + wrap_instr_fns! { + #![pymodule($m)] + + #(fn #fn_names(inputs: InstructionInput) -> InstructionOutput;)* + } + }; + } + + #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)] + pub enum Instr { + #(#instr_enumerants)* + } + + impl Instr { + #[cfg(feature = "native_instrs")] + pub fn get_native_fn(self) -> fn(InstructionInput) -> InstructionOutput { + match self { + #(#get_native_fn_match_cases)* + } + } + pub fn get_model_fn(self) -> fn(InstructionInput) -> InstructionOutput { + match self { + #(#get_model_fn_match_cases)* + } + } + pub fn get_used_input_registers(self) -> &'static [InstructionInputRegister] { + match self { + #(#get_used_input_registers_match_cases)* + } + } + pub fn name(self) -> &'static str { + match self { + #(#name_match_cases)* + } + } + pub const VALUES: &'static [Self] = &[ + #(Self::#enumerants,)* + ]; + } + + #[cfg(feature = "native_instrs")] + pub mod native_instrs { + use super::*; + + #(#native_fn_tokens)* + } + }) + } +} + +impl Parse for Instructions { + fn parse(input: ParseStream) -> syn::Result { + let mut instructions = Vec::new(); + while !input.is_empty() { + instructions.push(input.parse()?); + } + Ok(Self { instructions }) + } +} + +#[proc_macro] +pub fn instructions(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as Instructions); + match input.to_tokens() { + Ok(retval) => retval, + Err(err) => err.to_compile_error(), + } + .into() +} diff --git a/src/instr_models.rs b/src/instr_models.rs index 59ff2bb..61fd42a 100644 --- a/src/instr_models.rs +++ b/src/instr_models.rs @@ -1,27 +1,29 @@ -use crate::{ConditionRegister, InstructionInput, InstructionResult, OverflowFlags}; +use crate::{ + ConditionRegister, InstructionInput, InstructionOutput, InstructionResult, OverflowFlags, +}; macro_rules! create_instr_variants_ov_cr { ($fn:ident, $fno:ident, $fn_:ident, $fno_:ident, $iwidth:ident) => { pub fn $fn(inputs: InstructionInput) -> InstructionResult { - InstructionResult { + Ok(InstructionOutput { overflow: None, - ..$fno(inputs) - } + ..$fno(inputs)? + }) } pub fn $fn_(inputs: InstructionInput) -> InstructionResult { - let mut retval = $fno_(inputs); + 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 + Ok(retval) } pub fn $fno_(inputs: InstructionInput) -> InstructionResult { - let mut retval = $fno(inputs); + 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 + Ok(retval) } }; } @@ -29,11 +31,11 @@ macro_rules! create_instr_variants_ov_cr { macro_rules! create_instr_variants_cr { ($fn:ident, $fn_:ident, $iwidth:ident) => { pub fn $fn_(inputs: InstructionInput) -> InstructionResult { - let mut retval = $fn(inputs); + let mut retval = $fn(inputs)?; let result = retval.rt.expect("expected rt to be set"); let cr0 = ConditionRegister::from_signed_int(result as $iwidth, false); retval.cr0 = Some(cr0); - retval + Ok(retval) } }; } @@ -41,38 +43,38 @@ macro_rules! create_instr_variants_cr { create_instr_variants_ov_cr!(add, addo, add_, addo_, i64); pub fn addo(inputs: InstructionInput) -> InstructionResult { - let ra = inputs.ra as i64; - let rb = inputs.rb as i64; + let ra = inputs.try_get_ra()? as i64; + let rb = inputs.try_get_rb()? as i64; let (result, ov) = ra.overflowing_add(rb); let result = result as u64; let ov32 = (ra as i32).overflowing_add(rb as i32).1; - InstructionResult { + Ok(InstructionOutput { rt: Some(result), overflow: Some(OverflowFlags { so: ov, ov, ov32 }), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } create_instr_variants_ov_cr!(subf, subfo, subf_, subfo_, i64); pub fn subfo(inputs: InstructionInput) -> InstructionResult { - let ra = inputs.ra as i64; - let rb = inputs.rb as i64; + let ra = inputs.try_get_ra()? as i64; + let rb = inputs.try_get_rb()? as i64; let (result, ov) = rb.overflowing_sub(ra); let result = result as u64; let ov32 = (rb as i32).overflowing_sub(ra as i32).1; - InstructionResult { + Ok(InstructionOutput { rt: Some(result), overflow: Some(OverflowFlags { so: ov, ov, ov32 }), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } create_instr_variants_ov_cr!(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 dividend = i128::from(inputs.try_get_ra()? as i64) << 64; + let divisor = i128::from(inputs.try_get_rb()? as i64); let overflow; let result; if divisor == 0 || (divisor == -1 && dividend == i128::min_value()) { @@ -88,18 +90,18 @@ pub fn divdeo(inputs: InstructionInput) -> InstructionResult { overflow = false; } } - InstructionResult { + Ok(InstructionOutput { rt: Some(result), overflow: Some(OverflowFlags::from_overflow(overflow)), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } create_instr_variants_ov_cr!(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 dividend = u128::from(inputs.try_get_ra()?) << 64; + let divisor = u128::from(inputs.try_get_rb()?); let overflow; let result; if divisor == 0 { @@ -115,18 +117,18 @@ pub fn divdeuo(inputs: InstructionInput) -> InstructionResult { overflow = false; } } - InstructionResult { + Ok(InstructionOutput { rt: Some(result), overflow: Some(OverflowFlags::from_overflow(overflow)), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } create_instr_variants_ov_cr!(divd, divdo, divd_, divdo_, i64); pub fn divdo(inputs: InstructionInput) -> InstructionResult { - let dividend = inputs.ra as i64; - let divisor = inputs.rb as i64; + let dividend = inputs.try_get_ra()? as i64; + let divisor = inputs.try_get_rb()? as i64; let overflow; let result; if divisor == 0 || (divisor == -1 && dividend == i64::min_value()) { @@ -136,18 +138,18 @@ pub fn divdo(inputs: InstructionInput) -> InstructionResult { result = (dividend / divisor) as u64; overflow = false; } - InstructionResult { + Ok(InstructionOutput { rt: Some(result), overflow: Some(OverflowFlags::from_overflow(overflow)), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } create_instr_variants_ov_cr!(divdu, divduo, divdu_, divduo_, i64); pub fn divduo(inputs: InstructionInput) -> InstructionResult { - let dividend: u64 = inputs.ra; - let divisor: u64 = inputs.rb; + let dividend: u64 = inputs.try_get_ra()?; + let divisor: u64 = inputs.try_get_rb()?; let overflow; let result; if divisor == 0 { @@ -157,19 +159,19 @@ pub fn divduo(inputs: InstructionInput) -> InstructionResult { result = dividend / divisor; overflow = false; } - InstructionResult { + Ok(InstructionOutput { rt: Some(result), overflow: Some(OverflowFlags::from_overflow(overflow)), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } // ISA doesn't define compare results -- POWER9 apparently uses i64 instead of i32 create_instr_variants_ov_cr!(divwe, divweo, divwe_, divweo_, i64); pub fn divweo(inputs: InstructionInput) -> InstructionResult { - let dividend = i64::from(inputs.ra as i32) << 32; - let divisor = i64::from(inputs.rb as i32); + let dividend = i64::from(inputs.try_get_ra()? as i32) << 32; + let divisor = i64::from(inputs.try_get_rb()? as i32); let overflow; let result; if divisor == 0 || (divisor == -1 && dividend == i64::min_value()) { @@ -185,19 +187,19 @@ pub fn divweo(inputs: InstructionInput) -> InstructionResult { overflow = false; } } - InstructionResult { + Ok(InstructionOutput { rt: Some(result), overflow: Some(OverflowFlags::from_overflow(overflow)), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } // ISA doesn't define compare results -- POWER9 apparently uses i64 instead of i32 create_instr_variants_ov_cr!(divweu, divweuo, divweu_, divweuo_, i64); pub fn divweuo(inputs: InstructionInput) -> InstructionResult { - let dividend = u64::from(inputs.ra as u32) << 32; - let divisor = u64::from(inputs.rb as u32); + let dividend = u64::from(inputs.try_get_ra()? as u32) << 32; + let divisor = u64::from(inputs.try_get_rb()? as u32); let overflow; let result; if divisor == 0 { @@ -213,19 +215,19 @@ pub fn divweuo(inputs: InstructionInput) -> InstructionResult { overflow = false; } } - InstructionResult { + Ok(InstructionOutput { rt: Some(result), overflow: Some(OverflowFlags::from_overflow(overflow)), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } // ISA doesn't define compare results -- POWER9 apparently uses i64 instead of i32 create_instr_variants_ov_cr!(divw, divwo, divw_, divwo_, i64); pub fn divwo(inputs: InstructionInput) -> InstructionResult { - let dividend = inputs.ra as i32; - let divisor = inputs.rb as i32; + let dividend = inputs.try_get_ra()? as i32; + let divisor = inputs.try_get_rb()? as i32; let overflow; let result; if divisor == 0 || (divisor == -1 && dividend == i32::min_value()) { @@ -235,19 +237,19 @@ pub fn divwo(inputs: InstructionInput) -> InstructionResult { result = (dividend / divisor) as u32 as u64; overflow = false; } - InstructionResult { + Ok(InstructionOutput { rt: Some(result), overflow: Some(OverflowFlags::from_overflow(overflow)), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } // ISA doesn't define compare results -- POWER9 apparently uses i64 instead of i32 create_instr_variants_ov_cr!(divwu, divwuo, divwu_, divwuo_, i64); pub fn divwuo(inputs: InstructionInput) -> InstructionResult { - let dividend = inputs.ra as u32; - let divisor = inputs.rb as u32; + let dividend = inputs.try_get_ra()? as u32; + let divisor = inputs.try_get_rb()? as u32; let overflow; let result; if divisor == 0 { @@ -257,183 +259,183 @@ pub fn divwuo(inputs: InstructionInput) -> InstructionResult { result = (dividend / divisor) as u64; overflow = false; } - InstructionResult { + Ok(InstructionOutput { rt: Some(result), overflow: Some(OverflowFlags::from_overflow(overflow)), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } pub fn modsd(inputs: InstructionInput) -> InstructionResult { - let dividend = inputs.ra as i64; - let divisor = inputs.rb as i64; + let dividend = inputs.try_get_ra()? as i64; + let divisor = inputs.try_get_rb()? as i64; let result; if divisor == 0 || (divisor == -1 && dividend == i64::min_value()) { result = 0; } else { result = (dividend % divisor) as u64; } - InstructionResult { + Ok(InstructionOutput { rt: Some(result), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } pub fn modud(inputs: InstructionInput) -> InstructionResult { - let dividend: u64 = inputs.ra; - let divisor: u64 = inputs.rb; + let dividend: u64 = inputs.try_get_ra()?; + let divisor: u64 = inputs.try_get_rb()?; let result; if divisor == 0 { result = 0; } else { result = dividend % divisor; } - InstructionResult { + Ok(InstructionOutput { rt: Some(result), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } pub fn modsw(inputs: InstructionInput) -> InstructionResult { - let dividend = inputs.ra as i32; - let divisor = inputs.rb as i32; + let dividend = inputs.try_get_ra()? as i32; + let divisor = inputs.try_get_rb()? as i32; let result; if divisor == 0 || (divisor == -1 && dividend == i32::min_value()) { result = 0; } else { result = (dividend % divisor) as u64; } - InstructionResult { + Ok(InstructionOutput { rt: Some(result), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } pub fn moduw(inputs: InstructionInput) -> InstructionResult { - let dividend = inputs.ra as u32; - let divisor = inputs.rb as u32; + let dividend = inputs.try_get_ra()? as u32; + let divisor = inputs.try_get_rb()? as u32; let result; if divisor == 0 { result = 0; } else { result = (dividend % divisor) as u64; } - InstructionResult { + Ok(InstructionOutput { rt: Some(result), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } create_instr_variants_ov_cr!(mullw, mullwo, mullw_, mullwo_, i64); pub fn mullwo(inputs: InstructionInput) -> InstructionResult { - let ra = inputs.ra as i32 as i64; - let rb = inputs.rb as i32 as i64; + let ra = inputs.try_get_ra()? as i32 as i64; + let rb = inputs.try_get_rb()? as i32 as i64; let result = ra.wrapping_mul(rb) as u64; let overflow = result as i32 as i64 != result as i64; - InstructionResult { + Ok(InstructionOutput { rt: Some(result), overflow: Some(OverflowFlags::from_overflow(overflow)), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } create_instr_variants_cr!(mulhw, mulhw_, i32); pub fn mulhw(inputs: InstructionInput) -> InstructionResult { - let ra = inputs.ra as i32 as i64; - let rb = inputs.rb as i32 as i64; + let ra = inputs.try_get_ra()? as i32 as i64; + let rb = inputs.try_get_rb()? as i32 as i64; let result = (ra * rb) >> 32; let mut result = result as u32 as u64; result |= result << 32; - InstructionResult { + Ok(InstructionOutput { rt: Some(result), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } create_instr_variants_cr!(mulhwu, mulhwu_, i32); pub fn mulhwu(inputs: InstructionInput) -> InstructionResult { - let ra = inputs.ra as u32 as u64; - let rb = inputs.rb as u32 as u64; + let ra = inputs.try_get_ra()? as u32 as u64; + let rb = inputs.try_get_rb()? as u32 as u64; let result = (ra * rb) >> 32; let mut result = result as u32 as u64; result |= result << 32; - InstructionResult { + Ok(InstructionOutput { rt: Some(result), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } create_instr_variants_ov_cr!(mulld, mulldo, mulld_, mulldo_, i64); pub fn mulldo(inputs: InstructionInput) -> InstructionResult { - let ra = inputs.ra as i64; - let rb = inputs.rb as i64; + let ra = inputs.try_get_ra()? as i64; + let rb = inputs.try_get_rb()? as i64; let result = ra.wrapping_mul(rb) as u64; let overflow = ra.checked_mul(rb).is_none(); - InstructionResult { + Ok(InstructionOutput { rt: Some(result), overflow: Some(OverflowFlags::from_overflow(overflow)), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } create_instr_variants_cr!(mulhd, mulhd_, i64); pub fn mulhd(inputs: InstructionInput) -> InstructionResult { - let ra = inputs.ra as i64 as i128; - let rb = inputs.rb as i64 as i128; + let ra = inputs.try_get_ra()? as i64 as i128; + let rb = inputs.try_get_rb()? as i64 as i128; let result = ((ra * rb) >> 64) as i64; let result = result as u64; - InstructionResult { + Ok(InstructionOutput { rt: Some(result), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } create_instr_variants_cr!(mulhdu, mulhdu_, i64); pub fn mulhdu(inputs: InstructionInput) -> InstructionResult { - let ra = inputs.ra as u128; - let rb = inputs.rb as u128; + let ra = inputs.try_get_ra()? as u128; + let rb = inputs.try_get_rb()? as u128; let result = ((ra * rb) >> 64) as u64; - InstructionResult { + Ok(InstructionOutput { rt: Some(result), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } pub fn maddhd(inputs: InstructionInput) -> InstructionResult { - let ra = inputs.ra as i64 as i128; - let rb = inputs.rb as i64 as i128; - let rc = inputs.rc as i64 as i128; + let ra = inputs.try_get_ra()? as i64 as i128; + let rb = inputs.try_get_rb()? as i64 as i128; + let rc = inputs.try_get_rc()? as i64 as i128; let result = ((ra * rb + rc) >> 64) as u64; - InstructionResult { + Ok(InstructionOutput { rt: Some(result), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } pub fn maddhdu(inputs: InstructionInput) -> InstructionResult { - let ra = inputs.ra as u128; - let rb = inputs.rb as u128; - let rc = inputs.rc as u128; + let ra = inputs.try_get_ra()? as u128; + let rb = inputs.try_get_rb()? as u128; + let rc = inputs.try_get_rc()? as u128; let result = ((ra * rb + rc) >> 64) as u64; - InstructionResult { + Ok(InstructionOutput { rt: Some(result), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } pub fn maddld(inputs: InstructionInput) -> InstructionResult { - let ra = inputs.ra as i64; - let rb = inputs.rb as i64; - let rc = inputs.rc as i64; + let ra = inputs.try_get_ra()? as i64; + let rb = inputs.try_get_rb()? as i64; + let rc = inputs.try_get_rc()? as i64; let result = ra.wrapping_mul(rb).wrapping_add(rc) as u64; - InstructionResult { + Ok(InstructionOutput { rt: Some(result), - ..InstructionResult::default() - } + ..InstructionOutput::default() + }) } diff --git a/src/lib.rs b/src/lib.rs index a67adf2..0121e41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,13 +9,14 @@ compile_error!("native_instrs feature requires target_arch to be powerpc64"); pub mod instr_models; mod serde_hex; +use power_instruction_analyzer_proc_macro::instructions; use serde::{Deserialize, Serialize}; +use serde_plain::forward_display_to_serde; use std::{ cmp::Ordering, fmt, ops::{Index, IndexMut}, }; -use serde_plain::forward_display_to_serde; fn is_default(v: &T) -> bool { T::default() == *v @@ -139,7 +140,7 @@ impl ConditionRegister { } #[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct InstructionResult { +pub struct InstructionOutput { #[serde( default, skip_serializing_if = "Option::is_none", @@ -179,6 +180,10 @@ impl fmt::Display for MissingInstructionInput { } } +impl std::error::Error for MissingInstructionInput {} + +pub type InstructionResult = Result; + #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)] pub enum InstructionInputRegister { #[serde(rename = "ra")] @@ -187,7 +192,7 @@ pub enum InstructionInputRegister { Rb, #[serde(rename = "rc")] Rc, - #[serde(rename = "ca")] + #[serde(rename = "carry")] Carry, } @@ -195,8 +200,12 @@ forward_display_to_serde!(InstructionInputRegister); #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct InstructionInput { - #[serde(with = "serde_hex::SerdeHex")] - pub ra: u64, + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "serde_hex::SerdeHex" + )] + pub ra: Option, #[serde( default, skip_serializing_if = "Option::is_none", @@ -213,6 +222,39 @@ pub struct InstructionInput { pub carry: Option, } +macro_rules! impl_instr_try_get { + ( + $( + $vis:vis fn $fn:ident -> $return_type:ty { .$field:ident else $error_enum:ident } + )+ + ) => { + impl InstructionInput { + $( + $vis fn $fn(self) -> Result<$return_type, MissingInstructionInput> { + self.$field.ok_or(MissingInstructionInput { + input: InstructionInputRegister::$error_enum, + }) + } + )+ + } + }; +} + +impl_instr_try_get! { + pub fn try_get_ra -> u64 { + .ra else Ra + } + pub fn try_get_rb -> u64 { + .rb else Rb + } + pub fn try_get_rc -> u64 { + .rc else Rc + } + pub fn try_get_carry -> CarryFlags { + .carry else Carry + } +} + fn is_false(v: &bool) -> bool { !v } @@ -223,8 +265,8 @@ pub struct TestCase { #[serde(flatten)] pub inputs: InstructionInput, #[serde(default, skip_serializing_if = "Option::is_none")] - pub native_outputs: Option, - pub model_outputs: InstructionResult, + pub native_outputs: Option, + pub model_outputs: InstructionOutput, #[serde(default, skip_serializing_if = "is_false")] pub model_mismatch: bool, } @@ -359,6 +401,7 @@ macro_rules! instr { ra, rb, rc, + carry, } = inputs; let rt: u64; let xer: u64; @@ -379,7 +422,7 @@ macro_rules! instr { : "b"(ra), "b"(rb), "b"(rc), "b"(0u64), "b"(!0x8000_0000u64) : "xer", "cr"); } - let mut retval = InstructionResult::default(); + let mut retval = InstructionOutput::default(); map_instr_results!(rt, xer, cr, retval, [$($results)*]); retval } @@ -401,7 +444,7 @@ macro_rules! instrs { wrap_instr_fns! { #![pymodule($m)] - $(fn $fn(inputs: InstructionInput) -> InstructionResult;)* + $(fn $fn(inputs: InstructionInput) -> InstructionOutput;)* } }; } @@ -416,14 +459,14 @@ macro_rules! instrs { impl Instr { #[cfg(feature = "native_instrs")] - pub fn get_native_fn(self) -> fn(InstructionInput) -> InstructionResult { + pub fn get_native_fn(self) -> fn(InstructionInput) -> InstructionOutput { match self { $( Self::$enumerant => native_instrs::$fn, )+ } } - pub fn get_model_fn(self) -> fn(InstructionInput) -> InstructionResult { + pub fn get_model_fn(self) -> fn(InstructionInput) -> InstructionOutput { match self { $( Self::$enumerant => instr_models::$fn, @@ -467,299 +510,295 @@ macro_rules! instrs { }; } -instrs! { +instructions! { // add #[enumerant = Add] - fn add(ra, rb) -> (rt) { + fn add(Ra, Rb) -> (Rt) { "add" } #[enumerant = AddO] - fn addo(ra, rb) -> (rt, ov) { + fn addo(Ra, Rb) -> (Rt, Overflow) { "addo" } #[enumerant = Add_] - fn add_(ra, rb) -> (rt, cr0) { + fn add_(Ra, Rb) -> (Rt, CR0) { "add." } #[enumerant = AddO_] - fn addo_(ra, rb) -> (rt, ov, cr0) { + fn addo_(Ra, Rb) -> (Rt, Overflow, CR0) { "addo." } // subf #[enumerant = SubF] - fn subf(ra, rb) -> (rt) { + fn subf(Ra, Rb) -> (Rt) { "subf" } #[enumerant = SubFO] - fn subfo(ra, rb) -> (rt, ov) { + fn subfo(Ra, Rb) -> (Rt, Overflow) { "subfo" } #[enumerant = SubF_] - fn subf_(ra, rb) -> (rt, cr0) { + fn subf_(Ra, Rb) -> (Rt, CR0) { "subf." } #[enumerant = SubFO_] - fn subfo_(ra, rb) -> (rt, ov, cr0) { + fn subfo_(Ra, Rb) -> (Rt, Overflow, CR0) { "subfo." } // divde #[enumerant = DivDE] - fn divde(ra, rb) -> (rt) { + fn divde(Ra, Rb) -> (Rt) { "divde" } #[enumerant = DivDEO] - fn divdeo(ra, rb) -> (rt, ov) { + fn divdeo(Ra, Rb) -> (Rt, Overflow) { "divdeo" } #[enumerant = DivDE_] - fn divde_(ra, rb) -> (rt, cr0) { + fn divde_(Ra, Rb) -> (Rt, CR0) { "divde." } #[enumerant = DivDEO_] - fn divdeo_(ra, rb) -> (rt, ov, cr0) { + fn divdeo_(Ra, Rb) -> (Rt, Overflow, CR0) { "divdeo." } // divdeu #[enumerant = DivDEU] - fn divdeu(ra, rb) -> (rt) { + fn divdeu(Ra, Rb) -> (Rt) { "divdeu" } #[enumerant = DivDEUO] - fn divdeuo(ra, rb) -> (rt, ov) { + fn divdeuo(Ra, Rb) -> (Rt, Overflow) { "divdeuo" } #[enumerant = DivDEU_] - fn divdeu_(ra, rb) -> (rt, cr0) { + fn divdeu_(Ra, Rb) -> (Rt, CR0) { "divdeu." } #[enumerant = DivDEUO_] - fn divdeuo_(ra, rb) -> (rt, ov, cr0) { + fn divdeuo_(Ra, Rb) -> (Rt, Overflow, CR0) { "divdeuo." } // divd #[enumerant = DivD] - fn divd(ra, rb) -> (rt) { + fn divd(Ra, Rb) -> (Rt) { "divd" } #[enumerant = DivDO] - fn divdo(ra, rb) -> (rt, ov) { + fn divdo(Ra, Rb) -> (Rt, Overflow) { "divdo" } #[enumerant = DivD_] - fn divd_(ra, rb) -> (rt, cr0) { + fn divd_(Ra, Rb) -> (Rt, CR0) { "divd." } #[enumerant = DivDO_] - fn divdo_(ra, rb) -> (rt, ov, cr0) { + fn divdo_(Ra, Rb) -> (Rt, Overflow, CR0) { "divdo." } // divdu #[enumerant = DivDU] - fn divdu(ra, rb) -> (rt) { + fn divdu(Ra, Rb) -> (Rt) { "divdu" } #[enumerant = DivDUO] - fn divduo(ra, rb) -> (rt, ov) { + fn divduo(Ra, Rb) -> (Rt, Overflow) { "divduo" } #[enumerant = DivDU_] - fn divdu_(ra, rb) -> (rt, cr0) { + fn divdu_(Ra, Rb) -> (Rt, CR0) { "divdu." } #[enumerant = DivDUO_] - fn divduo_(ra, rb) -> (rt, ov, cr0) { + fn divduo_(Ra, Rb) -> (Rt, Overflow, CR0) { "divduo." } // divwe #[enumerant = DivWE] - fn divwe(ra, rb) -> (rt) { + fn divwe(Ra, Rb) -> (Rt) { "divwe" } #[enumerant = DivWEO] - fn divweo(ra, rb) -> (rt, ov) { + fn divweo(Ra, Rb) -> (Rt, Overflow) { "divweo" } #[enumerant = DivWE_] - fn divwe_(ra, rb) -> (rt, cr0) { + fn divwe_(Ra, Rb) -> (Rt, CR0) { "divwe." } #[enumerant = DivWEO_] - fn divweo_(ra, rb) -> (rt, ov, cr0) { + fn divweo_(Ra, Rb) -> (Rt, Overflow, CR0) { "divweo." } // divweu #[enumerant = DivWEU] - fn divweu(ra, rb) -> (rt) { + fn divweu(Ra, Rb) -> (Rt) { "divweu" } #[enumerant = DivWEUO] - fn divweuo(ra, rb) -> (rt, ov) { + fn divweuo(Ra, Rb) -> (Rt, Overflow) { "divweuo" } #[enumerant = DivWEU_] - fn divweu_(ra, rb) -> (rt, cr0) { + fn divweu_(Ra, Rb) -> (Rt, CR0) { "divweu." } #[enumerant = DivWEUO_] - fn divweuo_(ra, rb) -> (rt, ov, cr0) { + fn divweuo_(Ra, Rb) -> (Rt, Overflow, CR0) { "divweuo." } // divw #[enumerant = DivW] - fn divw(ra, rb) -> (rt) { + fn divw(Ra, Rb) -> (Rt) { "divw" } #[enumerant = DivWO] - fn divwo(ra, rb) -> (rt, ov) { + fn divwo(Ra, Rb) -> (Rt, Overflow) { "divwo" } #[enumerant = DivW_] - fn divw_(ra, rb) -> (rt, cr0) { + fn divw_(Ra, Rb) -> (Rt, CR0) { "divw." } #[enumerant = DivWO_] - fn divwo_(ra, rb) -> (rt, ov, cr0) { + fn divwo_(Ra, Rb) -> (Rt, Overflow, CR0) { "divwo." } // divwu #[enumerant = DivWU] - fn divwu(ra, rb) -> (rt) { + fn divwu(Ra, Rb) -> (Rt) { "divwu" } #[enumerant = DivWUO] - fn divwuo(ra, rb) -> (rt, ov) { + fn divwuo(Ra, Rb) -> (Rt, Overflow) { "divwuo" } #[enumerant = DivWU_] - fn divwu_(ra, rb) -> (rt, cr0) { + fn divwu_(Ra, Rb) -> (Rt, CR0) { "divwu." } #[enumerant = DivWUO_] - fn divwuo_(ra, rb) -> (rt, ov, cr0) { + fn divwuo_(Ra, Rb) -> (Rt, Overflow, CR0) { "divwuo." } // mod* #[enumerant = ModSD] - fn modsd(ra, rb) -> (rt) { + fn modsd(Ra, Rb) -> (Rt) { "modsd" } #[enumerant = ModUD] - fn modud(ra, rb) -> (rt) { + fn modud(Ra, Rb) -> (Rt) { "modud" } #[enumerant = ModSW] - fn modsw(ra, rb) -> (rt) { + fn modsw(Ra, Rb) -> (Rt) { "modsw" } #[enumerant = ModUW] - fn moduw(ra, rb) -> (rt) { + fn moduw(Ra, Rb) -> (Rt) { "moduw" } // mullw #[enumerant = MulLW] - fn mullw(ra, rb) -> (rt) { + fn mullw(Ra, Rb) -> (Rt) { "mullw" } #[enumerant = MulLWO] - fn mullwo(ra, rb) -> (rt, ov) { + fn mullwo(Ra, Rb) -> (Rt, Overflow) { "mullwo" } #[enumerant = MulLW_] - fn mullw_(ra, rb) -> (rt, cr0) { + fn mullw_(Ra, Rb) -> (Rt, CR0) { "mullw." } #[enumerant = MulLWO_] - fn mullwo_(ra, rb) -> (rt, ov, cr0) { + fn mullwo_(Ra, Rb) -> (Rt, Overflow, CR0) { "mullwo." } // mulhw #[enumerant = MulHW] - fn mulhw(ra, rb) -> (rt) { + fn mulhw(Ra, Rb) -> (Rt) { "mulhw" } #[enumerant = MulHW_] - fn mulhw_(ra, rb) -> (rt, cr0) { + fn mulhw_(Ra, Rb) -> (Rt, CR0) { "mulhw." } // mulhwu #[enumerant = MulHWU] - fn mulhwu(ra, rb) -> (rt) { + fn mulhwu(Ra, Rb) -> (Rt) { "mulhwu" } #[enumerant = MulHWU_] - fn mulhwu_(ra, rb) -> (rt, cr0) { + fn mulhwu_(Ra, Rb) -> (Rt, CR0) { "mulhwu." } // mulld #[enumerant = MulLD] - fn mulld(ra, rb) -> (rt) { + fn mulld(Ra, Rb) -> (Rt) { "mulld" } #[enumerant = MulLDO] - fn mulldo(ra, rb) -> (rt, ov) { + fn mulldo(Ra, Rb) -> (Rt, Overflow) { "mulldo" } #[enumerant = MulLD_] - fn mulld_(ra, rb) -> (rt, cr0) { + fn mulld_(Ra, Rb) -> (Rt, CR0) { "mulld." } #[enumerant = MulLDO_] - fn mulldo_(ra, rb) -> (rt, ov, cr0) { + fn mulldo_(Ra, Rb) -> (Rt, Overflow, CR0) { "mulldo." } // mulhd #[enumerant = MulHD] - fn mulhd(ra, rb) -> (rt) { + fn mulhd(Ra, Rb) -> (Rt) { "mulhd" } #[enumerant = MulHD_] - fn mulhd_(ra, rb) -> (rt, cr0) { + fn mulhd_(Ra, Rb) -> (Rt, CR0) { "mulhd." } // mulhdu #[enumerant = MulHDU] - fn mulhdu(ra, rb) -> (rt) { + fn mulhdu(Ra, Rb) -> (Rt) { "mulhdu" } #[enumerant = MulHDU_] - fn mulhdu_(ra, rb) -> (rt, cr0) { + fn mulhdu_(Ra, Rb) -> (Rt, CR0) { "mulhdu." } // madd* #[enumerant = MAddHD] - fn maddhd(ra, rb, rc) -> (rt) { + fn maddhd(Ra, Rb, Rc) -> (Rt) { "maddhd" } #[enumerant = MAddHDU] - fn maddhdu(ra, rb, rc) -> (rt) { + fn maddhdu(Ra, Rb, Rc) -> (Rt) { "maddhdu" } #[enumerant = MAddLD] - fn maddld(ra, rb, rc) -> (rt) { + fn maddld(Ra, Rb, Rc) -> (Rt) { "maddld" } } // must be after instrs macro call since it uses a macro definition mod python; -} - -// must be after instrs macro call since it uses a macro definition -mod python; diff --git a/src/python.rs b/src/python.rs index 40153de..e3313eb 100644 --- a/src/python.rs +++ b/src/python.rs @@ -4,7 +4,7 @@ #![cfg(feature = "python")] use crate::{ - CarryFlags, ConditionRegister, Instr, InstructionInput, InstructionResult, OverflowFlags, + CarryFlags, ConditionRegister, Instr, InstructionInput, InstructionOutput, OverflowFlags, }; use pyo3::{prelude::*, wrap_pyfunction, PyObjectProtocol}; use std::{borrow::Cow, cell::RefCell, fmt}; @@ -286,11 +286,11 @@ fn power_instruction_analyzer(_py: Python, m: &PyModule) -> PyResult<()> { #[pymodule(m)] #[pyclass(name = InstructionInput)] #[wrapped(value: InstructionInput)] - #[args(ra, rb="None", rc="None", carry="None")] + #[args(ra="None", rb="None", rc="None", carry="None")] #[text_signature = "(ra, rb, rc, carry)"] struct PyInstructionInput { #[set = set_ra] - ra: u64, + ra: Option, #[set = set_rb] rb: Option, #[set = set_rc] @@ -302,11 +302,12 @@ fn power_instruction_analyzer(_py: Python, m: &PyModule) -> PyResult<()> { wrap_type! { #[pymodule(m)] - #[pyclass(name = InstructionResult)] - #[wrapped(value: InstructionResult)] + #[pyclass(name = InstructionOutput)] + #[wrapped(value: InstructionOutput)] #[args( rt="None", overflow="None", + carry="None", cr0="None", cr1="None", cr2="None", @@ -319,6 +320,7 @@ fn power_instruction_analyzer(_py: Python, m: &PyModule) -> PyResult<()> { #[text_signature = "(\ rt=None, \ overflow=None, \ + carry=None, \ cr0=None, \ cr1=None, \ cr2=None, \ @@ -328,11 +330,13 @@ fn power_instruction_analyzer(_py: Python, m: &PyModule) -> PyResult<()> { cr6=None, \ cr7=None)" ] - struct PyInstructionResult { + struct PyInstructionOutput { #[set = set_rt] rt: Option, #[set = set_overflow] overflow: Option, + #[set = set_carry] + carry: Option, #[set = set_cr0] cr0: Option, #[set = set_cr1] diff --git a/tests/test_power_instruction_analyzer.py b/tests/test_power_instruction_analyzer.py index 3fb483f..f4da122 100644 --- a/tests/test_power_instruction_analyzer.py +++ b/tests/test_power_instruction_analyzer.py @@ -83,14 +83,14 @@ class TestInstructionInput(unittest.TestCase): "InstructionInput(ra=123, rb=456, rc=789)") -class TestInstructionResult(unittest.TestCase): +class TestInstructionOutput(unittest.TestCase): def test_text_signature(self): - self.assertEqual(pia.InstructionResult.__text_signature__, + self.assertEqual(pia.InstructionOutput.__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( + v = pia.InstructionOutput( overflow=pia.OverflowFlags(so=False, ov=False, ov32=True)) self.assertIsNone(v.rt) self.assertIsNotNone(v.overflow) @@ -113,7 +113,7 @@ class TestInstructionResult(unittest.TestCase): self.assertIsNotNone(v.cr2) def test_str_repr(self): - v = pia.InstructionResult( + v = pia.InstructionOutput( 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)) @@ -122,7 +122,7 @@ class TestInstructionResult(unittest.TestCase): + '"cr0":{"lt":true,"gt":true,"eq":true,"so":true},' + '"cr2":{"lt":false,"gt":false,"eq":false,"so":false}}') self.assertEqual(repr(v), - "InstructionResult(rt=None, " + "InstructionOutput(rt=None, " + "overflow=OverflowFlags(so=False, ov=False, ov32=True), " + "cr0=ConditionRegister(lt=True, gt=True, eq=True, so=True), " + "cr1=None, " @@ -139,7 +139,7 @@ class TestDivInstrs(unittest.TestCase): fn = getattr(pia, fn_name) self.assertEqual(fn.__text_signature__, "(inputs)") results = fn(v) - self.assertIsInstance(results, pia.InstructionResult) + self.assertIsInstance(results, pia.InstructionOutput) if __name__ == "__main__":