From: Jacob Lifshay Date: Wed, 8 Jul 2020 06:26:26 +0000 (-0700) Subject: add python bindings X-Git-Tag: v0.2.0~46 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=884aab583568a556347d596670c9f0416723c255;p=power-instruction-analyzer.git add python bindings --- diff --git a/.gitignore b/.gitignore index ea8c4bf..b33ee44 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /target +__pycache__ +*.pyc \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index a7b295a..65c6009 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,19 +1,179 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cloudabi" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ctor" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39858aa5bac06462d4dd4b9164848eb81ffc4aa5c479746393598fd193afa227" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "ghost" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5bcf1bbeab73aa4cf2fde60a846858dc036163c7c33bec309f8d17de785479" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "indoc" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47741a8bc60fb26eb8d6e0238bbb26d8575ff623fdc97b1a2c00c050b9684ed8" +dependencies = [ + "indoc-impl", + "proc-macro-hack", +] + +[[package]] +name = "indoc-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce046d161f000fffde5f432a0d034d0341dc152643b2598ed5bfce44c4f3a8f0" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", + "unindent", +] + +[[package]] +name = "instant" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b141fdc7836c525d4d594027d318c84161ca17aaf8113ab1f81ab93ae897485" + +[[package]] +name = "inventory" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "621b50c176968fd3b0bd71f821a28a0ea98db2b5aea966b2fbb8bd1b7d310328" +dependencies = [ + "ctor", + "ghost", + "inventory-impl", +] + +[[package]] +name = "inventory-impl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f99a4111304bade76468d05beab3487c226e4fe4c4de1c4e8f006e815762db73" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "itoa" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" +[[package]] +name = "libc" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" + +[[package]] +name = "lock_api" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "parking_lot" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" +dependencies = [ + "cfg-if", + "cloudabi", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "paste" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" +dependencies = [ + "paste-impl", + "proc-macro-hack", +] + +[[package]] +name = "paste-impl" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" +dependencies = [ + "proc-macro-hack", +] + [[package]] name = "power-instruction-analyzer" version = "0.1.0" dependencies = [ + "pyo3", "serde", "serde_json", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" + [[package]] name = "proc-macro2" version = "1.0.17" @@ -23,6 +183,44 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "pyo3" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca8710ffa8211c9a62a8a3863c4267c710dc42a82a7fd29c97de465d7ea6b7d" +dependencies = [ + "ctor", + "indoc", + "inventory", + "libc", + "parking_lot", + "paste", + "pyo3cls", + "unindent", +] + +[[package]] +name = "pyo3-derive-backend" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ad070bf6967b0d29ea74931ffcf9c6bbe8402a726e9afbeafadc0a287cc2b3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pyo3cls" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fa17e1ea569d0bf3b7c00f2a9eea831ca05e55dd76f1794c541abba1c64baa" +dependencies = [ + "pyo3-derive-backend", + "quote", + "syn", +] + [[package]] name = "quote" version = "1.0.6" @@ -32,12 +230,24 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" + [[package]] name = "ryu" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "serde" version = "1.0.110" @@ -69,6 +279,12 @@ dependencies = [ "serde", ] +[[package]] +name = "smallvec" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" + [[package]] name = "syn" version = "1.0.27" @@ -85,3 +301,31 @@ name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" + +[[package]] +name = "unindent" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af41d708427f8fd0e915dcebb2cae0f0e6acb2a939b2d399c265c39a38a18942" + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 14461b0..54b4f5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,14 @@ license = "LGPL-2.1-or-later" [features] native_instrs = [] -default = ["native_instrs"] +python = ["pyo3"] +python-extension = ["python", "pyo3/extension-module"] + +[lib] +name = "power_instruction_analyzer" +crate-type = ["rlib", "cdylib"] [dependencies] serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" \ No newline at end of file +serde_json = "1.0" +pyo3 = { version = "0.11", optional = true } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1b8821c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# See Notices.txt for copyright information +[build-system] +requires = ["maturin"] +build-backend = "maturin" + +[tool.maturin] +bindings = "pyo3" +cargo-extra-args = "--features python-extension" diff --git a/src/instr_models.rs b/src/instr_models.rs index 0c6c76a..4b64aff 100644 --- a/src/instr_models.rs +++ b/src/instr_models.rs @@ -1,6 +1,6 @@ -use crate::{OverflowFlags, TestDivInput, TestDivResult}; +use crate::{DivInput, DivResult, OverflowFlags}; -pub fn divdeo(inputs: TestDivInput) -> TestDivResult { +pub fn divdeo(inputs: DivInput) -> DivResult { let dividend = i128::from(inputs.dividend as i64) << 64; let divisor = i128::from(inputs.divisor as i64); let overflow; @@ -18,7 +18,7 @@ pub fn divdeo(inputs: TestDivInput) -> TestDivResult { overflow = false; } } - TestDivResult { + DivResult { result, overflow: Some(OverflowFlags { overflow, @@ -27,7 +27,7 @@ pub fn divdeo(inputs: TestDivInput) -> TestDivResult { } } -pub fn divdeuo(inputs: TestDivInput) -> TestDivResult { +pub fn divdeuo(inputs: DivInput) -> DivResult { let dividend = u128::from(inputs.dividend) << 64; let divisor = u128::from(inputs.divisor); let overflow; @@ -45,7 +45,7 @@ pub fn divdeuo(inputs: TestDivInput) -> TestDivResult { overflow = false; } } - TestDivResult { + DivResult { result, overflow: Some(OverflowFlags { overflow, @@ -54,7 +54,7 @@ pub fn divdeuo(inputs: TestDivInput) -> TestDivResult { } } -pub fn divdo(inputs: TestDivInput) -> TestDivResult { +pub fn divdo(inputs: DivInput) -> DivResult { let dividend = inputs.dividend as i64; let divisor = inputs.divisor as i64; let overflow; @@ -66,7 +66,7 @@ pub fn divdo(inputs: TestDivInput) -> TestDivResult { result = (dividend / divisor) as u64; overflow = false; } - TestDivResult { + DivResult { result, overflow: Some(OverflowFlags { overflow, @@ -75,7 +75,7 @@ pub fn divdo(inputs: TestDivInput) -> TestDivResult { } } -pub fn divduo(inputs: TestDivInput) -> TestDivResult { +pub fn divduo(inputs: DivInput) -> DivResult { let dividend: u64 = inputs.dividend; let divisor: u64 = inputs.divisor; let overflow; @@ -87,7 +87,7 @@ pub fn divduo(inputs: TestDivInput) -> TestDivResult { result = dividend / divisor; overflow = false; } - TestDivResult { + DivResult { result, overflow: Some(OverflowFlags { overflow, @@ -96,7 +96,7 @@ pub fn divduo(inputs: TestDivInput) -> TestDivResult { } } -pub fn divweo(inputs: TestDivInput) -> TestDivResult { +pub fn divweo(inputs: DivInput) -> DivResult { let dividend = i64::from(inputs.dividend as i32) << 32; let divisor = i64::from(inputs.divisor as i32); let overflow; @@ -114,7 +114,7 @@ pub fn divweo(inputs: TestDivInput) -> TestDivResult { overflow = false; } } - TestDivResult { + DivResult { result, overflow: Some(OverflowFlags { overflow, @@ -123,7 +123,7 @@ pub fn divweo(inputs: TestDivInput) -> TestDivResult { } } -pub fn divweuo(inputs: TestDivInput) -> TestDivResult { +pub fn divweuo(inputs: DivInput) -> DivResult { let dividend = u64::from(inputs.dividend as u32) << 32; let divisor = u64::from(inputs.divisor as u32); let overflow; @@ -141,7 +141,7 @@ pub fn divweuo(inputs: TestDivInput) -> TestDivResult { overflow = false; } } - TestDivResult { + DivResult { result, overflow: Some(OverflowFlags { overflow, @@ -150,7 +150,7 @@ pub fn divweuo(inputs: TestDivInput) -> TestDivResult { } } -pub fn divwo(inputs: TestDivInput) -> TestDivResult { +pub fn divwo(inputs: DivInput) -> DivResult { let dividend = inputs.dividend as i32; let divisor = inputs.divisor as i32; let overflow; @@ -162,7 +162,7 @@ pub fn divwo(inputs: TestDivInput) -> TestDivResult { result = (dividend / divisor) as u32 as u64; overflow = false; } - TestDivResult { + DivResult { result, overflow: Some(OverflowFlags { overflow, @@ -171,7 +171,7 @@ pub fn divwo(inputs: TestDivInput) -> TestDivResult { } } -pub fn divwuo(inputs: TestDivInput) -> TestDivResult { +pub fn divwuo(inputs: DivInput) -> DivResult { let dividend = inputs.dividend as u32; let divisor = inputs.divisor as u32; let overflow; @@ -183,7 +183,7 @@ pub fn divwuo(inputs: TestDivInput) -> TestDivResult { result = (dividend / divisor) as u64; overflow = false; } - TestDivResult { + DivResult { result, overflow: Some(OverflowFlags { overflow, @@ -192,7 +192,7 @@ pub fn divwuo(inputs: TestDivInput) -> TestDivResult { } } -pub fn modsd(inputs: TestDivInput) -> TestDivResult { +pub fn modsd(inputs: DivInput) -> DivResult { let dividend = inputs.dividend as i64; let divisor = inputs.divisor as i64; let result; @@ -201,13 +201,13 @@ pub fn modsd(inputs: TestDivInput) -> TestDivResult { } else { result = (dividend % divisor) as u64; } - TestDivResult { + DivResult { result, overflow: None, } } -pub fn modud(inputs: TestDivInput) -> TestDivResult { +pub fn modud(inputs: DivInput) -> DivResult { let dividend: u64 = inputs.dividend; let divisor: u64 = inputs.divisor; let result; @@ -216,13 +216,13 @@ pub fn modud(inputs: TestDivInput) -> TestDivResult { } else { result = dividend % divisor; } - TestDivResult { + DivResult { result, overflow: None, } } -pub fn modsw(inputs: TestDivInput) -> TestDivResult { +pub fn modsw(inputs: DivInput) -> DivResult { let dividend = inputs.dividend as i32; let divisor = inputs.divisor as i32; let result; @@ -231,13 +231,13 @@ pub fn modsw(inputs: TestDivInput) -> TestDivResult { } else { result = (dividend % divisor) as u64; } - TestDivResult { + DivResult { result, overflow: None, } } -pub fn moduw(inputs: TestDivInput) -> TestDivResult { +pub fn moduw(inputs: DivInput) -> DivResult { let dividend = inputs.dividend as u32; let divisor = inputs.divisor as u32; let result; @@ -246,7 +246,7 @@ pub fn moduw(inputs: TestDivInput) -> TestDivResult { } else { result = (dividend % divisor) as u64; } - TestDivResult { + DivResult { result, overflow: None, } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d6a8082 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// See Notices.txt for copyright information + +#![cfg_attr(feature = "native_instrs", feature(llvm_asm))] + +#[cfg(all(feature = "native_instrs", not(target_arch = "powerpc64")))] +compile_error!("native_instrs feature requires target_arch to be powerpc64"); + +pub mod instr_models; +mod python; +mod serde_hex; + +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct OverflowFlags { + pub overflow: bool, + pub overflow32: bool, +} + +impl OverflowFlags { + pub fn from_xer(xer: u64) -> Self { + Self { + overflow: (xer & 0x4000_0000) != 0, + overflow32: (xer & 0x8_0000) != 0, + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct DivResult { + #[serde(with = "serde_hex::SerdeHex")] + pub result: u64, + #[serde(default, flatten, skip_serializing_if = "Option::is_none")] + pub overflow: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct DivInput { + #[serde(with = "serde_hex::SerdeHex")] + pub dividend: u64, + #[serde(with = "serde_hex::SerdeHex")] + pub divisor: u64, + #[serde(default, with = "serde_hex::SerdeHex")] + pub result_prev: u64, +} + +fn is_false(v: &bool) -> bool { + !v +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct TestDivCase { + pub instr: DivInstr, + #[serde(flatten)] + pub inputs: DivInput, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub native_outputs: Option, + pub model_outputs: DivResult, + #[serde(default, skip_serializing_if = "is_false")] + pub model_mismatch: bool, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct WholeTest { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub test_div_cases: Vec, + pub any_model_mismatch: bool, +} + +macro_rules! make_div_functions { + ( + #[div] + { + $($div_enum:ident = $div_fn:ident ($div_instr:literal),)+ + } + #[rem] + { + $($rem_enum:ident = $rem_fn:ident ($rem_instr:literal),)+ + } + ) => { + #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)] + pub enum DivInstr { + $( + #[serde(rename = $div_instr)] + $div_enum, + )+ + $( + #[serde(rename = $rem_instr)] + $rem_enum, + )+ + } + + impl DivInstr { + #[cfg(feature = "native_instrs")] + pub fn get_native_fn(self) -> fn(DivInput) -> DivResult { + match self { + $( + Self::$div_enum => native_instrs::$div_fn, + )+ + $( + Self::$rem_enum => native_instrs::$rem_fn, + )+ + } + } + pub fn get_model_fn(self) -> fn(DivInput) -> DivResult { + match self { + $( + Self::$div_enum => instr_models::$div_fn, + )+ + $( + Self::$rem_enum => instr_models::$rem_fn, + )+ + } + } + pub fn name(self) -> &'static str { + match self { + $( + Self::$div_enum => $div_instr, + )+ + $( + Self::$rem_enum => $rem_instr, + )+ + } + } + pub const VALUES: &'static [Self] = &[ + $( + Self::$div_enum, + )+ + $( + Self::$rem_enum, + )+ + ]; + } + + #[cfg(feature = "native_instrs")] + pub mod native_instrs { + 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, + } + } + )+ + } + }; +} + +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"), + } + #[rem] + { + ModSD = modsd("modsd"), + ModUD = modud("modud"), + ModSW = modsw("modsw"), + ModUW = moduw("moduw"), + } +} diff --git a/src/main.rs b/src/main.rs index 60c2a12..cc16cf0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,211 +1,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later // See Notices.txt for copyright information -#![cfg_attr(feature = "native_instrs", feature(llvm_asm))] - -mod instr_models; -mod serde_hex; - -use serde::{Deserialize, Serialize}; - -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct OverflowFlags { - pub overflow: bool, - pub overflow32: bool, -} - -impl OverflowFlags { - pub fn from_xer(xer: u64) -> Self { - Self { - overflow: (xer & 0x4000_0000) != 0, - overflow32: (xer & 0x8_0000) != 0, - } - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct TestDivResult { - #[serde(with = "serde_hex::SerdeHex")] - pub result: u64, - #[serde(default, flatten, skip_serializing_if = "Option::is_none")] - pub overflow: Option, -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] -pub struct TestDivInput { - #[serde(with = "serde_hex::SerdeHex")] - pub dividend: u64, - #[serde(with = "serde_hex::SerdeHex")] - pub divisor: u64, - #[serde(with = "serde_hex::SerdeHex")] - pub result_prev: u64, -} - -fn is_false(v: &bool) -> bool { - !v -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] -pub struct TestDivCase { - pub instr: TestDivInstr, - #[serde(flatten)] - pub inputs: TestDivInput, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub native_outputs: Option, - pub model_outputs: TestDivResult, - #[serde(default, skip_serializing_if = "is_false")] - pub model_mismatch: bool, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct WholeTest { - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub test_div_cases: Vec, - pub any_model_mismatch: bool, -} - -macro_rules! make_div_functions { - ( - #[div] - { - $($div_enum:ident = $div_fn:ident ($div_instr:literal),)+ - } - #[rem] - { - $($rem_enum:ident = $rem_fn:ident ($rem_instr:literal),)+ - } - ) => { - #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)] - pub enum TestDivInstr { - $( - #[serde(rename = $div_instr)] - $div_enum, - )+ - $( - #[serde(rename = $rem_instr)] - $rem_enum, - )+ - } - - impl TestDivInstr { - #[cfg(feature = "native_instrs")] - pub fn get_native_fn(self) -> fn(TestDivInput) -> TestDivResult { - match self { - $( - Self::$div_enum => native_instrs::$div_fn, - )+ - $( - Self::$rem_enum => native_instrs::$rem_fn, - )+ - } - } - pub fn get_model_fn(self) -> fn(TestDivInput) -> TestDivResult { - match self { - $( - Self::$div_enum => instr_models::$div_fn, - )+ - $( - Self::$rem_enum => instr_models::$rem_fn, - )+ - } - } - pub fn name(self) -> &'static str { - match self { - $( - Self::$div_enum => $div_instr, - )+ - $( - Self::$rem_enum => $rem_instr, - )+ - } - } - pub const VALUES: &'static [Self] = &[ - $( - Self::$div_enum, - )+ - $( - Self::$rem_enum, - )+ - ]; - } - - #[cfg(feature = "native_instrs")] - mod native_instrs { - use super::*; - - $( - pub fn $div_fn(inputs: TestDivInput) -> TestDivResult { - let TestDivInput { - 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"); - } - TestDivResult { - result, - overflow: Some(OverflowFlags::from_xer(xer)), - } - } - )+ - $( - pub fn $rem_fn(inputs: TestDivInput) -> TestDivResult { - let TestDivInput { - 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)); - } - TestDivResult { - result, - overflow: None, - } - } - )+ - } - }; -} - -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"), - } - #[rem] - { - ModSD = modsd("modsd"), - ModUD = modud("modud"), - ModSW = modsw("modsw"), - ModUW = moduw("moduw"), - } -} +use power_instruction_analyzer::{DivInput, DivInstr, TestDivCase, WholeTest}; const TEST_VALUES: &[u64] = &[ 0x0, @@ -224,10 +20,10 @@ const TEST_VALUES: &[u64] = &[ fn main() { let mut test_div_cases = Vec::new(); let mut any_model_mismatch = false; - for &instr in TestDivInstr::VALUES { + for &instr in DivInstr::VALUES { for ÷nd in TEST_VALUES { for &divisor in TEST_VALUES { - let inputs = TestDivInput { + let inputs = DivInput { dividend, divisor, result_prev: 0xFECD_BA98_7654_3210, diff --git a/src/python.rs b/src/python.rs new file mode 100644 index 0000000..220663e --- /dev/null +++ b/src/python.rs @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// See Notices.txt for copyright information + +#![cfg(feature = "python")] + +use crate::{DivInput, DivResult, OverflowFlags}; +use pyo3::{prelude::*, wrap_pyfunction, PyObjectProtocol}; +use std::{borrow::Cow, cell::RefCell, fmt}; + +trait ToPythonRepr { + fn to_python_repr(&self) -> Cow { + struct Helper(RefCell>); + + impl) -> fmt::Result> fmt::Display for Helper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.borrow_mut().take().unwrap()(f) + } + } + + impl) -> fmt::Result> Helper { + fn new(f: T) -> Self { + Helper(RefCell::new(Some(f))) + } + } + Cow::Owned(format!( + "{}", + Helper::new(|f: &mut fmt::Formatter<'_>| -> fmt::Result { self.write(f) }) + )) + } + fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.to_python_repr()) + } +} + +fn write_list_body_to_python_repr, T: ToPythonRepr>( + list: I, + f: &mut fmt::Formatter<'_>, + separator: &str, +) -> fmt::Result { + let mut first = true; + for i in list { + if first { + first = false; + } else { + f.write_str(separator)?; + } + i.write(f)?; + } + Ok(()) +} + +struct NamedArgPythonRepr<'a> { + name: &'a str, + value: &'a (dyn ToPythonRepr + 'a), +} + +impl ToPythonRepr for NamedArgPythonRepr<'_> { + fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.name)?; + f.write_str("=")?; + self.value.write(f) + } +} + +impl ToPythonRepr for &'_ T { + fn to_python_repr(&self) -> Cow { + (**self).to_python_repr() + } + fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).write(f) + } +} + +impl ToPythonRepr for bool { + fn to_python_repr(&self) -> Cow { + Cow::Borrowed(match self { + true => "True", + false => "False", + }) + } +} + +impl ToPythonRepr for Option { + fn to_python_repr(&self) -> Cow { + match self { + Some(v) => v.to_python_repr(), + None => Cow::Borrowed("None"), + } + } + fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Some(v) => v.write(f), + None => f.write_str("None"), + } + } +} + +impl ToPythonRepr for Vec { + fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("[")?; + write_list_body_to_python_repr(self, f, ", ")?; + f.write_str("]") + } +} + +macro_rules! impl_int_to_python_repr { + ($($int:ident,)*) => { + $( + impl ToPythonRepr for $int { + fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self) + } + } + )* + }; +} + +impl_int_to_python_repr! {u8, u16, u32, u64, u128, i8, i16, i32, i64, i128,} + +macro_rules! wrap_type { + ( + #[pymodule($m:expr)] + // use tt to work around PyO3 bug fixed in PyO3#832 + #[pyclass $($pyclass_args:tt)?] + #[wrapped($value:ident: $wrapped:ident)] + $(#[$meta:meta])* + struct $wrapper:ident { + $( + #[set=$setter_name:ident] + $(#[$field_meta:meta])* + $field_name:ident:$field_type:ty, + )* + } + ) => { + #[pyclass $($pyclass_args)?] + $(#[$meta])* + #[derive(Clone)] + struct $wrapper { + $value: $wrapped, + } + + impl<'source> FromPyObject<'source> for $wrapped { + fn extract(ob: &'source PyAny) -> PyResult { + Ok(ob.extract::<$wrapper>()?.$value) + } + } + + impl IntoPy for $wrapped { + fn into_py(self, py: Python) -> PyObject { + $wrapper { $value: self }.into_py(py) + } + } + + #[pymethods] + impl $wrapper { + #[new] + fn new($($field_name:$field_type),*) -> Self { + Self { + $value: $wrapped { + $($field_name),* + } + } + } + $( + #[getter] + $(#[$field_meta:meta])* + fn $field_name(&self) -> $field_type { + self.$value.$field_name + } + #[setter] + fn $setter_name(&mut self, $field_name: $field_type) { + self.$value.$field_name = $field_name; + } + )* + } + + impl ToPythonRepr for $wrapped { + fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(concat!(stringify!($wrapped), "("))?; + write_list_body_to_python_repr(&[ + $( + NamedArgPythonRepr { + name: stringify!($field_name), + value: &self.$field_name, + }, + )* + ], f, ", ")?; + f.write_str(")") + } + } + + #[pyproto] + impl PyObjectProtocol for $wrapper { + fn __str__(&self) -> String { + serde_json::to_string(&self.$value).unwrap() + } + fn __repr__(&self) -> String { + self.$value.to_python_repr().into_owned() + } + } + + $m.add_class::<$wrapper>()?; + }; +} + +macro_rules! wrap_instr_fns { + ( + #![pymodule($m:ident)] + $( + // use tt to work around PyO3 bug fixed in PyO3#832 + $(#[pyfunction $pyfunction_args:tt])? + $(#[$meta:meta])* + fn $name:ident(inputs: $inputs:ty) -> $result:ty; + )* + ) => { + $( + { + #[pyfunction $($pyfunction_args)?] + #[text_signature = "(inputs)"] + $(#[$meta])* + fn $name(inputs: $inputs) -> $result { + $crate::instr_models::$name(inputs) + } + + $m.add_wrapped(wrap_pyfunction!($name))?; + } + )* + }; +} + +#[pymodule] +fn power_instruction_analyzer(_py: Python, m: &PyModule) -> PyResult<()> { + wrap_type! { + #[pymodule(m)] + #[pyclass(name = OverflowFlags)] + #[wrapped(value: OverflowFlags)] + #[text_signature = "(overflow, overflow32)"] + struct PyOverflowFlags { + #[set = set_overflow] + overflow: bool, + #[set = set_overflow32] + overflow32: 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, + } + } + + wrap_type! { + #[pymodule(m)] + #[pyclass(name = DivResult)] + #[wrapped(value: DivResult)] + #[text_signature = "(result, overflow)"] + struct PyDivResult { + #[set = set_result] + result: u64, + #[set = set_overflow] + overflow: 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; + } + Ok(()) +} diff --git a/src/serde_hex.rs b/src/serde_hex.rs index c3f4bd0..17ec8a8 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::{Deserialize, Deserializer, Serializer}; +use serde::{de, Deserialize, Deserializer, Serializer}; pub(crate) trait SerdeHex { fn serialize(&self, serializer: S) -> Result; @@ -10,12 +10,28 @@ pub(crate) trait SerdeHex { Self: Sized; } -impl SerdeHex for u64 { - fn serialize(&self, serializer: S) -> Result { - serializer.serialize_str(&format!("{:#X}", self)) - } - fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result { - let _text: &str = Deserialize::deserialize(deserializer)?; - todo!("parse text as 0x") - } +macro_rules! impl_hex_for_uint { + ($ty:ty) => { + impl SerdeHex for $ty { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&format!("{:#X}", self)) + } + fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result { + let text: &str = Deserialize::deserialize(deserializer)?; + const PREFIX: &str = "0x"; + if text.starts_with(PREFIX) { + let hex_digits = &text[PREFIX.len()..]; + Self::from_str_radix(hex_digits, 16).map_err(de::Error::custom) + } else { + Err(de::Error::custom("hexadecimal field must start with 0x")) + } + } + } + }; } + +impl_hex_for_uint!(u8); +impl_hex_for_uint!(u16); +impl_hex_for_uint!(u32); +impl_hex_for_uint!(u64); +impl_hex_for_uint!(u128); diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_power_instruction_analyzer.py b/tests/test_power_instruction_analyzer.py new file mode 100644 index 0000000..da954a0 --- /dev/null +++ b/tests/test_power_instruction_analyzer.py @@ -0,0 +1,107 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# See Notices.txt for copyright information + +import unittest +import power_instruction_analyzer as pia + + +class TestOverflowFlags(unittest.TestCase): + def test_text_signature(self): + self.assertEqual(pia.OverflowFlags.__text_signature__, + "(overflow, overflow32)") + + 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) + + def test_str_repr(self): + v = pia.OverflowFlags(overflow=False, overflow32=True) + self.assertEqual(str(v), + '{"overflow":false,"overflow32":true}') + self.assertEqual(repr(v), + "OverflowFlags(overflow=False, overflow32=True)") + + +class TestDivInput(unittest.TestCase): + def test_text_signature(self): + self.assertEqual(pia.DivInput.__text_signature__, + "(dividend, divisor, result_prev)") + + 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) + + def test_str_repr(self): + v = pia.DivInput(dividend=123, divisor=456, result_prev=789) + self.assertEqual(str(v), + '{"dividend":"0x7B","divisor":"0x1C8","result_prev":"0x315"}') + self.assertEqual(repr(v), + "DivInput(dividend=123, divisor=456, result_prev=789)") + + +class TestDivResult(unittest.TestCase): + def test_text_signature(self): + self.assertEqual(pia.DivResult.__text_signature__, + "(result, overflow)") + + def test_fields(self): + v = pia.DivResult(result=1234, + overflow=pia.OverflowFlags(overflow=False, overflow32=True)) + self.assertEqual(v.result, 1234) + self.assertIsNotNone(v.overflow) + self.assertEqual(v.overflow.overflow, False) + self.assertEqual(v.overflow.overflow32, True) + v.result = 123 + self.assertEqual(v.result, 123) + v.overflow = None + self.assertIsNone(v.overflow) + + def test_str_repr(self): + v = pia.DivResult(result=1234, + overflow=pia.OverflowFlags(overflow=False, overflow32=True)) + self.assertEqual(str(v), + '{"result":"0x4D2","overflow":false,"overflow32":true}') + self.assertEqual(repr(v), + "DivResult(result=1234, overflow=OverflowFlags(overflow=False, overflow32=True))") + + +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): + fn = getattr(pia, fn_name) + results = fn(v) + self.assertEqual(str(results), expected) + + +if __name__ == "__main__": + unittest.main()