Compare commits

...

8 Commits
v0.3.3 ... main

25 changed files with 25927 additions and 8776 deletions

235
Cargo.lock generated
View File

@ -4,9 +4,9 @@ version = 3
[[package]]
name = "anstream"
version = "0.6.15"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
dependencies = [
"anstyle",
"anstyle-parse",
@ -19,64 +19,65 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.8"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-parse"
version = "0.2.5"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.1"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.4"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
"once_cell_polyfill",
"windows-sys 0.59.0",
]
[[package]]
name = "anyhow"
version = "1.0.87"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "autocfg"
version = "1.3.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "clap"
version = "4.5.17"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.17"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
dependencies = [
"anstream",
"anstyle",
@ -86,61 +87,68 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.2"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "colorchoice"
version = "1.0.2"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "colored"
version = "2.1.0"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]
name = "deranged"
version = "0.3.11"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
dependencies = [
"powerfmt",
]
[[package]]
name = "equivalent"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "hashbrown"
version = "0.14.5"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
[[package]]
name = "hermit-abi"
version = "0.3.9"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "indexmap"
version = "2.5.0"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
dependencies = [
"equivalent",
"hashbrown",
"serde",
]
[[package]]
@ -151,9 +159,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
version = "1.0.11"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "lazy_static"
@ -163,15 +171,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.158"
version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "log"
version = "0.4.22"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "num-conv"
@ -190,9 +198,9 @@ dependencies = [
[[package]]
name = "num_cpus"
version = "1.16.0"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
dependencies = [
"hermit-abi",
"libc",
@ -208,19 +216,26 @@ dependencies = [
]
[[package]]
name = "phf"
version = "0.11.2"
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
name = "phf"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7"
dependencies = [
"phf_shared",
"serde",
]
[[package]]
name = "phf_codegen"
version = "0.11.2"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
checksum = "efbdcb6f01d193b17f0b9c3360fa7e0e620991b193ff08702f78b3ce365d7e61"
dependencies = [
"phf_generator",
"phf_shared",
@ -228,19 +243,19 @@ dependencies = [
[[package]]
name = "phf_generator"
version = "0.11.2"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
checksum = "2cbb1126afed61dd6368748dae63b1ee7dc480191c6262a3b4ff1e29d86a6c5b"
dependencies = [
"fastrand",
"phf_shared",
"rand",
]
[[package]]
name = "phf_shared"
version = "0.11.2"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981"
dependencies = [
"siphasher",
]
@ -252,33 +267,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppc750cl"
version = "0.3.3"
name = "powerpc"
version = "0.4.1"
[[package]]
name = "ppc750cl-asm"
version = "0.3.3"
name = "powerpc-asm"
version = "0.4.1"
dependencies = [
"phf",
"thiserror",
]
[[package]]
name = "ppc750cl-fuzz"
version = "0.3.3"
name = "powerpc-fuzz"
version = "0.4.1"
dependencies = [
"clap",
"num_cpus",
"ppc750cl",
"powerpc",
]
[[package]]
name = "ppc750cl-genisa"
version = "0.3.3"
name = "powerpc-genisa"
version = "0.4.1"
dependencies = [
"anyhow",
"indexmap",
"log",
"num-traits",
"phf",
"phf_codegen",
"prettyplease",
"proc-macro2",
@ -291,9 +307,9 @@ dependencies = [
[[package]]
name = "prettyplease"
version = "0.2.22"
version = "0.2.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba"
checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a"
dependencies = [
"proc-macro2",
"syn",
@ -301,57 +317,42 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.86"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "ryu"
version = "1.0.18"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "serde"
version = "1.0.210"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.210"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
@ -385,9 +386,9 @@ dependencies = [
[[package]]
name = "siphasher"
version = "0.3.11"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "strsim"
@ -397,9 +398,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.77"
version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [
"proc-macro2",
"quote",
@ -407,10 +408,30 @@ dependencies = [
]
[[package]]
name = "time"
version = "0.3.36"
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.3.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
dependencies = [
"deranged",
"itoa",
@ -425,15 +446,15 @@ dependencies = [
[[package]]
name = "time-core"
version = "0.1.2"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
[[package]]
name = "time-macros"
version = "0.2.18"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
dependencies = [
"num-conv",
"time-core",
@ -441,9 +462,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.12"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unsafe-libyaml"
@ -468,9 +489,9 @@ dependencies = [
[[package]]
name = "windows-sys"
version = "0.52.0"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]

View File

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

101
README.md
View File

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

View File

@ -1,14 +1,14 @@
[package]
name = "ppc750cl-asm"
name = "powerpc-asm"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
description = "Assembler for PowerPC 750CL"
description = "PowerPC assembler"
readme = "../README.md"
keywords.workspace = true
repository.workspace = true
documentation = "https://docs.rs/ppc750cl-asm"
documentation = "https://docs.rs/powerpc-asm"
rust-version.workspace = true
[features]
@ -16,4 +16,5 @@ default = ["std"]
std = []
[dependencies]
phf = "0.11"
phf = "0.12"
thiserror = "2.0"

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,22 @@
use crate::Arguments;
use core::fmt::Formatter;
use thiserror::Error;
#[derive(Debug)]
#[derive(Error, Debug)]
pub enum ArgumentError {
#[error(
"argument index {index} is out of range: {value} (expected between {start} and {end})"
)]
ArgOutOfRangeUnsigned { index: usize, value: u32, start: u32, end: u32 },
#[error(
"argument index {index} is out of range: {value} (expected between {start} and {end})"
)]
ArgOutOfRangeSigned { index: usize, value: i32, start: i32, end: i32 },
#[error("unexpected number of arguments: {value} (expected at most {expected})")]
ArgCount { value: usize, expected: usize },
#[error("unknown instruction mnemonic")]
UnknownMnemonic,
}
impl core::fmt::Display for ArgumentError {
fn fmt(&self, _f: &mut Formatter<'_>) -> core::fmt::Result {
todo!()
}
}
#[cfg(feature = "std")]
impl std::error::Error for ArgumentError {}
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
pub enum Argument {
#[default]

View File

@ -1,24 +1,24 @@
use ppc750cl_asm::*;
use Argument::{None, Signed as S, Unsigned as U};
use powerpc_asm::*;
use Argument::{None as N, Signed as S, Unsigned as U};
macro_rules! assert_asm {
($mnemonic:literal, $arg1:expr, $arg2:expr, $arg3:expr, $arg4:expr, $arg5: expr, $code:literal) => {{
assert_eq!(assemble($mnemonic, &[$arg1, $arg2, $arg3, $arg4, $arg5]).unwrap(), $code)
}};
($mnemonic:literal, $arg1:expr, $arg2:expr, $arg3:expr, $arg4:expr, $code:literal) => {{
assert_eq!(assemble($mnemonic, &[$arg1, $arg2, $arg3, $arg4, None]).unwrap(), $code)
assert_eq!(assemble($mnemonic, &[$arg1, $arg2, $arg3, $arg4, N]).unwrap(), $code)
}};
($mnemonic:literal, $arg1:expr, $arg2:expr, $arg3:expr, $code:literal) => {{
assert_eq!(assemble($mnemonic, &[$arg1, $arg2, $arg3, None, None]).unwrap(), $code)
assert_eq!(assemble($mnemonic, &[$arg1, $arg2, $arg3, N, N]).unwrap(), $code)
}};
($mnemonic:literal, $arg1:expr, $arg2:expr, $code:literal) => {{
assert_eq!(assemble($mnemonic, &[$arg1, $arg2, None, None, None]).unwrap(), $code)
assert_eq!(assemble($mnemonic, &[$arg1, $arg2, N, N, N]).unwrap(), $code)
}};
($mnemonic:literal, $arg1:expr, $code:literal) => {{
assert_eq!(assemble($mnemonic, &[$arg1, None, None, None, None]).unwrap(), $code)
assert_eq!(assemble($mnemonic, &[$arg1, N, N, N, N]).unwrap(), $code)
}};
($mnemonic:literal, $code:literal) => {{
assert_eq!(assemble($mnemonic, &[None, None, None, None, None]).unwrap(), $code)
assert_eq!(assemble($mnemonic, &[N, N, N, N, N]).unwrap(), $code)
}};
}
@ -180,3 +180,38 @@ fn test_ins_xor() {
assert_asm!("xor", U(5), U(0), U(5), 0x7C052A78); // xor r5, r0, r5
assert_asm!("xor.", U(7), U(9), U(10), 0x7D275279); // xor. r7, r9, r10
}
#[test]
fn test_sync() {
assert_asm!("sync", 0x7C0004AC); // sync
assert_asm!("sync", U(0), 0x7C0004AC); // sync 0
assert_asm!("lwsync", 0x7C2004AC); // lwsync
assert_asm!("sync", U(1), 0x7C2004AC); // sync 1
assert_asm!("ptesync", 0x7C4004AC); // ptesync
assert_asm!("sync", U(2), 0x7C4004AC); // sync 2
}
#[test]
fn test_rldcl() {
assert_asm!("rldcl", U(3), U(0), U(6), U(27), 0x780336D0); // rldcl r3, r0, r6, 27
assert_asm!("rotld", U(3), U(0), U(6), 0x78033010); // rotld r3, r0, r6
}
#[test]
fn test_rldic() {
assert_asm!("rldic", U(5), U(6), U(3), U(36), 0x78C51928); // rldic r5, r6, 3, 36
}
#[test]
fn test_rldicl() {
assert_asm!("rldicl", U(5), U(6), U(0), U(32), 0x78C50020); // rldicl r5, r6, 0, 32
assert_asm!("rldicl", U(11), U(29), U(0), U(62), 0x7BAB07A0); // rldicl r11, r29, 0, 62
}
#[test]
fn test_dss() {
assert_asm!("dss", U(1), U(0), 0x7C20066C); // dss 1, 0
assert_asm!("dss", U(1), 0x7C20066C); // dss 1
assert_asm!("dss", U(0), U(1), 0x7E00066C); // dss 0, 1
assert_asm!("dssall", 0x7E00066C); // dssall
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,821 @@
use powerpc::{Extension, Extensions, Ins};
const EXTENSIONS: Extensions = Extensions::from_extension(Extension::AltiVec);
macro_rules! assert_asm {
($ins:ident, $disasm:literal) => {{
assert_eq!(format!("{}", $ins.simplified()), $disasm)
}};
($code:literal, $disasm:literal) => {{
let ins = Ins::new($code, EXTENSIONS);
assert_eq!(format!("{}", ins.simplified()), $disasm)
}};
}
#[test]
fn test_vec_dss() {
assert_asm!(0x7C60066C, "dss 3");
assert_asm!(0x7E00066C, "dssall");
}
#[test]
fn test_vec_dst() {
assert_asm!(0x7C6742AC, "dst r7, r8, 3");
assert_asm!(0x7E232AAC, "dstt r3, r5, 1");
}
#[test]
fn test_vec_dstst() {
assert_asm!(0x7C44FAEC, "dstst r4, r31, 2");
assert_asm!(0x7E63DAEC, "dststt r3, r27, 3");
}
#[test]
fn test_vec_lvebx() {
assert_asm!(0x7C64380E, "lvebx v3, r4, r7");
}
#[test]
fn test_vec_lvehx() {
assert_asm!(0x7CA8F84E, "lvehx v5, r8, r31");
}
#[test]
fn test_vec_lvewx() {
assert_asm!(0x7D09508E, "lvewx v8, r9, r10");
}
#[test]
fn test_vec_lvsl() {
assert_asm!(0x7CA6480C, "lvsl v5, r6, r9");
}
#[test]
fn test_vec_lvsr() {
assert_asm!(0x7C60284C, "lvsr v3, r0, r5");
}
#[test]
fn test_vec_lvx() {
assert_asm!(0x7CF2E8CE, "lvx v7, r18, r29");
}
#[test]
fn test_vec_lvxl() {
assert_asm!(0x7D17FACE, "lvxl v8, r23, r31");
}
#[test]
fn test_vec_mfvscr() {
assert_asm!(0x13E00604, "mfvscr v31");
}
#[test]
fn test_vec_mtvscr() {
assert_asm!(0x1000CE44, "mtvscr v25");
}
#[test]
fn test_vec_stvebx() {
assert_asm!(0x7CE3210E, "stvebx v7, r3, r4");
}
#[test]
fn test_vec_stvehx() {
assert_asm!(0x7F25514E, "stvehx v25, r5, r10");
}
#[test]
fn test_vec_stvewx() {
assert_asm!(0x7E08498E, "stvewx v16, r8, r9");
}
#[test]
fn test_vec_stvx() {
assert_asm!(0x7FE039CE, "stvx v31, r0, r7");
}
#[test]
fn test_vec_stvxl() {
assert_asm!(0x7E8EF3CE, "stvxl v20, r14, r30");
}
#[test]
fn test_vec_vaddcuw() {
assert_asm!(0x10A63980, "vaddcuw v5, v6, v7");
}
#[test]
fn test_vec_vaddfp() {
assert_asm!(0x13BEF80A, "vaddfp v29, v30, v31");
}
#[test]
fn test_vec_vaddsbs() {
assert_asm!(0x10035300, "vaddsbs v0, v3, v10");
}
#[test]
fn test_vec_vaddsws() {
assert_asm!(0x11043B80, "vaddsws v8, v4, v7");
}
#[test]
fn test_vec_vaddubm() {
assert_asm!(0x100CE000, "vaddubm v0, v12, v28");
}
#[test]
fn test_vec_vaddubs() {
assert_asm!(0x10AACA00, "vaddubs v5, v10, v25");
}
#[test]
fn test_vec_vadduhm() {
assert_asm!(0x1112E040, "vadduhm v8, v18, v28");
}
#[test]
fn test_vec_vadduhs() {
assert_asm!(0x1071EA40, "vadduhs v3, v17, v29");
}
#[test]
fn test_vec_vadduwm() {
assert_asm!(0x10D6F080, "vadduwm v6, v22, v30");
}
#[test]
fn test_vec_vadduws() {
assert_asm!(0x1109B280, "vadduws v8, v9, v22");
}
#[test]
fn test_vec_vand() {
assert_asm!(0x1156BC04, "vand v10, v22, v23");
}
#[test]
fn test_vec_vandc() {
assert_asm!(0x10F4F444, "vandc v7, v20, v30");
}
#[test]
fn test_vec_vavgsb() {
assert_asm!(0x10BC1D02, "vavgsb v5, v28, v3");
}
#[test]
fn test_vec_vavgsh() {
assert_asm!(0x106DBD42, "vavgsh v3, v13, v23");
}
#[test]
fn test_vec_vavgsw() {
assert_asm!(0x112CAD82, "vavgsw v9, v12, v21");
}
#[test]
fn test_vec_vavgub() {
assert_asm!(0x100FF402, "vavgub v0, v15, v30");
}
#[test]
fn test_vec_vavguh() {
assert_asm!(0x108EC442, "vavguh v4, v14, v24");
}
#[test]
fn test_vec_vavguw() {
assert_asm!(0x10674482, "vavguw v3, v7, v8");
}
#[test]
fn test_vec_vcfsx() {
assert_asm!(0x101D534A, "vcfsx v0, v10, 0x1d");
}
#[test]
fn test_vec_vcfux() {
assert_asm!(0x10B03B0A, "vcfux v5, v7, 0x10");
}
#[test]
fn test_vec_vcmpbfp() {
assert_asm!(0x106963C6, "vcmpbfp v3, v9, v12");
assert_asm!(0x10E84FC6, "vcmpbfp. v7, v8, v9");
}
#[test]
fn test_vec_vcmpeqfp() {
assert_asm!(0x108640C6, "vcmpeqfp v4, v6, v8");
assert_asm!(0x10A304C6, "vcmpeqfp. v5, v3, v0");
}
#[test]
fn test_vec_vcmpequb() {
assert_asm!(0x1011D806, "vcmpequb v0, v17, v27");
assert_asm!(0x10649C06, "vcmpequb. v3, v4, v19");
}
#[test]
fn test_vec_vcmpequh() {
assert_asm!(0x10A86046, "vcmpequh v5, v8, v12");
assert_asm!(0x10E60446, "vcmpequh. v7, v6, v0");
}
#[test]
fn test_vec_vcmpequw() {
assert_asm!(0x10664086, "vcmpequw v3, v6, v8");
assert_asm!(0x10A85486, "vcmpequw. v5, v8, v10");
}
#[test]
fn test_vec_vcmpgefp() {
assert_asm!(0x100329C6, "vcmpgefp v0, v3, v5");
assert_asm!(0x108545C6, "vcmpgefp. v4, v5, v8");
}
#[test]
fn test_vec_vcmpgtfp() {
assert_asm!(0x10A0CAC6, "vcmpgtfp v5, v0, v25");
assert_asm!(0x10E3A6C6, "vcmpgtfp. v7, v3, v20");
}
#[test]
fn test_vec_vcmpgtsb() {
assert_asm!(0x10602306, "vcmpgtsb v3, v0, v4");
assert_asm!(0x10E88706, "vcmpgtsb. v7, v8, v16");
}
#[test]
fn test_vec_vcmpgtsh() {
assert_asm!(0x10A69B46, "vcmpgtsh v5, v6, v19");
assert_asm!(0x1192C746, "vcmpgtsh. v12, v18, v24");
}
#[test]
fn test_vec_vcmpgtsw() {
assert_asm!(0x1140F386, "vcmpgtsw v10, v0, v30");
assert_asm!(0x1297DF86, "vcmpgtsw. v20, v23, v27");
}
#[test]
fn test_vec_vcmpgtub() {
assert_asm!(0x10A88206, "vcmpgtub v5, v8, v16");
assert_asm!(0x13CAA606, "vcmpgtub. v30, v10, v20");
}
#[test]
fn test_vec_vcmpgtuh() {
assert_asm!(0x101BFA46, "vcmpgtuh v0, v27, v31");
assert_asm!(0x10853646, "vcmpgtuh. v4, v5, v6");
}
#[test]
fn test_vec_vcmpgtuw() {
assert_asm!(0x1070D286, "vcmpgtuw v3, v16, v26");
assert_asm!(0x10FAFE86, "vcmpgtuw. v7, v26, v31");
}
#[test]
fn test_vec_vctsxs() {
assert_asm!(0x10743BCA, "vctsxs v3, v7, 0x14");
}
#[test]
fn test_vec_vctuxs() {
assert_asm!(0x10AB638A, "vctuxs v5, v12, 0xb");
}
#[test]
fn test_vec_vexptefp() {
assert_asm!(0x10E0518A, "vexptefp v7, v10");
}
#[test]
fn test_vec_vlogefp() {
assert_asm!(0x100031CA, "vlogefp v0, v6");
}
#[test]
fn test_vec_vmaddfp() {
assert_asm!(0x1003396E, "vmaddfp v0, v3, v5, v7");
}
#[test]
fn test_vec_vmaxfp() {
assert_asm!(0x10C84C0A, "vmaxfp v6, v8, v9");
}
#[test]
fn test_vec_vmaxsb() {
assert_asm!(0x100AB102, "vmaxsb v0, v10, v22");
}
#[test]
fn test_vec_vmaxsh() {
assert_asm!(0x1298E142, "vmaxsh v20, v24, v28");
}
#[test]
fn test_vec_vmaxsw() {
assert_asm!(0x13DF6182, "vmaxsw v30, v31, v12");
}
#[test]
fn test_vec_vmaxub() {
assert_asm!(0x1198F002, "vmaxub v12, v24, v30");
}
#[test]
fn test_vec_vmaxuh() {
assert_asm!(0x1236D842, "vmaxuh v17, v22, v27");
}
#[test]
fn test_vec_vmaxuw() {
assert_asm!(0x114CC082, "vmaxuw v10, v12, v24");
}
#[test]
fn test_vec_vmhaddshs() {
assert_asm!(0x10A63A20, "vmhaddshs v5, v6, v7, v8");
}
#[test]
fn test_vec_vmhraddshs() {
assert_asm!(0x112A63A1, "vmhraddshs v9, v10, v12, v14");
}
#[test]
fn test_vec_vminfp() {
assert_asm!(0x106AAC4A, "vminfp v3, v10, v21");
}
#[test]
fn test_vec_vminsb() {
assert_asm!(0x10643B02, "vminsb v3, v4, v7");
}
#[test]
fn test_vec_vminsh() {
assert_asm!(0x10E9B342, "vminsh v7, v9, v22");
}
#[test]
fn test_vec_vminsw() {
assert_asm!(0x118F9382, "vminsw v12, v15, v18");
}
#[test]
fn test_vec_vminub() {
assert_asm!(0x108ED202, "vminub v4, v14, v26");
}
#[test]
fn test_vec_vminuh() {
assert_asm!(0x11F19A42, "vminuh v15, v17, v19");
}
#[test]
fn test_vec_vminuw() {
assert_asm!(0x1254F282, "vminuw v18, v20, v30");
}
#[test]
fn test_vec_vmladduhm() {
assert_asm!(0x10608762, "vmladduhm v3, v0, v16, v29");
}
#[test]
fn test_vec_vmrghb() {
assert_asm!(0x10F4A80C, "vmrghb v7, v20, v21");
}
#[test]
fn test_vec_vmrghh() {
assert_asm!(0x110AC84C, "vmrghh v8, v10, v25");
}
#[test]
fn test_vec_vmrghw() {
assert_asm!(0x1198E08C, "vmrghw v12, v24, v28");
}
#[test]
fn test_vec_vmrglb() {
assert_asm!(0x1299F10C, "vmrglb v20, v25, v30");
}
#[test]
fn test_vec_vmrglh() {
assert_asm!(0x131CF94C, "vmrglh v24, v28, v31");
}
#[test]
fn test_vec_vmrglw() {
assert_asm!(0x13DF018C, "vmrglw v30, v31, v0");
}
#[test]
fn test_vec_vmsummbm() {
assert_asm!(0x10044325, "vmsummbm v0, v4, v8, v12");
}
#[test]
fn test_vec_vmsumshm() {
assert_asm!(0x1114DFE8, "vmsumshm v8, v20, v27, v31");
}
#[test]
fn test_vec_vmsumshs() {
assert_asm!(0x1150ADE9, "vmsumshs v10, v16, v21, v23");
}
#[test]
fn test_vec_vmsumubm() {
assert_asm!(0x1198D7A4, "vmsumubm v12, v24, v26, v30");
}
#[test]
fn test_vec_vmsumuhm() {
assert_asm!(0x13C503E6, "vmsumuhm v30, v5, v0, v15");
}
#[test]
fn test_vec_vmsumuhs() {
assert_asm!(0x10032167, "vmsumuhs v0, v3, v4, v5");
}
#[test]
fn test_vec_vmulesb() {
assert_asm!(0x110EC308, "vmulesb v8, v14, v24");
}
#[test]
fn test_vec_vmulesh() {
assert_asm!(0x10602B48, "vmulesh v3, v0, v5");
}
#[test]
fn test_vec_vmuleub() {
assert_asm!(0x10076208, "vmuleub v0, v7, v12");
}
#[test]
fn test_vec_vmuleuh() {
assert_asm!(0x1200FA48, "vmuleuh v16, v0, v31");
}
#[test]
fn test_vec_vmulosb() {
assert_asm!(0x11E01908, "vmulosb v15, v0, v3");
}
#[test]
fn test_vec_vmulosh() {
assert_asm!(0x10685148, "vmulosh v3, v8, v10");
}
#[test]
fn test_vec_vmuloub() {
assert_asm!(0x10854008, "vmuloub v4, v5, v8");
}
#[test]
fn test_vec_vmulouh() {
assert_asm!(0x10A70048, "vmulouh v5, v7, v0");
}
#[test]
fn test_vec_vnmsubfp() {
assert_asm!(0x1060F42F, "vnmsubfp v3, v0, v16, v30");
}
#[test]
fn test_vec_vnor() {
assert_asm!(0x10605504, "vnor v3, v0, v10");
assert_asm!(0x10884504, "vnot v4, v8");
}
#[test]
fn test_vec_vor() {
assert_asm!(0x100D7C84, "vor v0, v13, v15");
assert_asm!(0x1077BC84, "vmr v3, v23");
}
#[test]
fn test_vec_vperm() {
assert_asm!(0x10a5302b, "vperm v5, v5, v6, v0");
}
#[test]
fn test_vec_vpkpx() {
assert_asm!(0x10AFE30E, "vpkpx v5, v15, v28");
}
#[test]
fn test_vec_vpkshss() {
assert_asm!(0x1006498E, "vpkshss v0, v6, v9");
}
#[test]
fn test_vec_vpkshus() {
assert_asm!(0x1220990E, "vpkshus v17, v0, v19");
}
#[test]
fn test_vec_vpkswss() {
assert_asm!(0x1253A1CE, "vpkswss v18, v19, v20");
}
#[test]
fn test_vec_vpkswus() {
assert_asm!(0x128AF14E, "vpkswus v20, v10, v30");
}
#[test]
fn test_vec_vpkuhum() {
assert_asm!(0x10BBA00E, "vpkuhum v5, v27, v20");
}
#[test]
fn test_vec_vpkuhus() {
assert_asm!(0x11AE788E, "vpkuhus v13, v14, v15");
}
#[test]
fn test_vec_vpkuwum() {
assert_asm!(0x114B604E, "vpkuwum v10, v11, v12");
}
#[test]
fn test_vec_vpkuwus() {
assert_asm!(0x1176F8CE, "vpkuwus v11, v22, v31");
}
#[test]
fn test_vec_vrefp() {
assert_asm!(0x1180C10A, "vrefp v12, v24");
}
#[test]
fn test_vec_vrfim() {
assert_asm!(0x1240F2CA, "vrfim v18, v30");
}
#[test]
fn test_vec_vrfin() {
assert_asm!(0x1140620A, "vrfin v10, v12");
}
#[test]
fn test_vec_vrfip() {
assert_asm!(0x10E08A8A, "vrfip v7, v17");
}
#[test]
fn test_vec_vrfiz() {
assert_asm!(0x1000A24A, "vrfiz v0, v20");
}
#[test]
fn test_vec_vrlb() {
assert_asm!(0x10EF8804, "vrlb v7, v15, v17");
}
#[test]
fn test_vec_vrlh() {
assert_asm!(0x12129844, "vrlh v16, v18, v19");
}
#[test]
fn test_vec_vrlw() {
assert_asm!(0x11540084, "vrlw v10, v20, v0");
}
#[test]
fn test_vec_vrsqrtefp() {
assert_asm!(0x1060794A, "vrsqrtefp v3, v15");
}
#[test]
fn test_vec_vsel() {
assert_asm!(0x100329AA, "vsel v0, v3, v5, v6");
}
#[test]
fn test_vec_vsl() {
assert_asm!(0x108CC1C4, "vsl v4, v12, v24");
}
#[test]
fn test_vec_vslb() {
assert_asm!(0x114E9104, "vslb v10, v14, v18");
}
#[test]
fn test_vec_vsldoi() {
assert_asm!(0x10601B6C, "vsldoi v3, v0, v3, 13");
}
#[test]
fn test_vec_vslh() {
assert_asm!(0x10AFC144, "vslh v5, v15, v24");
}
#[test]
fn test_vec_vslo() {
assert_asm!(0x10F1DC0C, "vslo v7, v17, v27");
}
#[test]
fn test_vec_vslw() {
assert_asm!(0x11128184, "vslw v8, v18, v16");
}
#[test]
fn test_vec_vspltb() {
assert_asm!(0x115C620C, "vspltb v10, v12, 0x1c");
}
#[test]
fn test_vec_vsplth() {
assert_asm!(0x11947A4C, "vsplth v12, v15, 0x14");
}
#[test]
fn test_vec_vspltisb() {
assert_asm!(0x1076030C, "vspltisb v3, -0xa");
}
#[test]
fn test_vec_vspltish() {
assert_asm!(0x11CE034C, "vspltish v14, 0xe");
}
#[test]
fn test_vec_vspltisw() {
assert_asm!(0x124C038C, "vspltisw v18, 0xc");
}
#[test]
fn test_vec_vspltw() {
assert_asm!(0x1018528C, "vspltw v0, v10, 0x18");
}
#[test]
fn test_vec_vsr() {
assert_asm!(0x116C6AC4, "vsr v11, v12, v13");
}
#[test]
fn test_vec_vsrab() {
assert_asm!(0x11D09304, "vsrab v14, v16, v18");
}
#[test]
fn test_vec_vsrah() {
assert_asm!(0x10E8C344, "vsrah v7, v8, v24");
}
#[test]
fn test_vec_vsraw() {
assert_asm!(0x112CAB84, "vsraw v9, v12, v21");
}
#[test]
fn test_vec_vsrb() {
assert_asm!(0x1112D204, "vsrb v8, v18, v26");
}
#[test]
fn test_vec_vsrh() {
assert_asm!(0x114C8244, "vsrh v10, v12, v16");
}
#[test]
fn test_vec_vsro() {
assert_asm!(0x118F9C4C, "vsro v12, v15, v19");
}
#[test]
fn test_vec_vsrw() {
assert_asm!(0x100EA284, "vsrw v0, v14, v20");
}
#[test]
fn test_vec_vsubcuw() {
assert_asm!(0x10AF8580, "vsubcuw v5, v15, v16");
}
#[test]
fn test_vec_vsubfp() {
assert_asm!(0x1080584A, "vsubfp v4, v0, v11");
}
#[test]
fn test_vec_vsubsbs() {
assert_asm!(0x10D2BF00, "vsubsbs v6, v18, v23");
}
#[test]
fn test_vec_vsubshs() {
assert_asm!(0x10F16740, "vsubshs v7, v17, v12");
}
#[test]
fn test_vec_vsubsws() {
assert_asm!(0x118D5780, "vsubsws v12, v13, v10");
}
#[test]
fn test_vec_vsububm() {
assert_asm!(0x11402C00, "vsububm v10, v0, v5");
}
#[test]
fn test_vec_vsububs() {
assert_asm!(0x10033600, "vsububs v0, v3, v6");
}
#[test]
fn test_vec_vsubuhm() {
assert_asm!(0x10EB6C40, "vsubuhm v7, v11, v13");
}
#[test]
fn test_vec_vsubuhs() {
assert_asm!(0x110A6640, "vsubuhs v8, v10, v12");
}
#[test]
fn test_vec_vsubuwm() {
assert_asm!(0x112BDC80, "vsubuwm v9, v11, v27");
}
#[test]
fn test_vec_vsubuws() {
assert_asm!(0x1149AE80, "vsubuws v10, v9, v21");
}
#[test]
fn test_vec_vsumsws() {
assert_asm!(0x116C6F88, "vsumsws v11, v12, v13");
}
#[test]
fn test_vec_vsum2sws() {
assert_asm!(0x11909688, "vsum2sws v12, v16, v18");
}
#[test]
fn test_vec_vsum4sbs() {
assert_asm!(0x11B19708, "vsum4sbs v13, v17, v18");
}
#[test]
fn test_vec_vsum4shs() {
assert_asm!(0x1296C648, "vsum4shs v20, v22, v24");
}
#[test]
fn test_vec_vsum4ubs() {
assert_asm!(0x12F8CE08, "vsum4ubs v23, v24, v25");
}
#[test]
fn test_vec_vupkhpx() {
assert_asm!(0x10A0434E, "vupkhpx v5, v8");
}
#[test]
fn test_vec_vupkhsb() {
assert_asm!(0x10001A0E, "vupkhsb v0, v3");
}
#[test]
fn test_vec_vupkhsh() {
assert_asm!(0x11806A4E, "vupkhsh v12, v13");
}
#[test]
fn test_vec_vupklpx() {
assert_asm!(0x108083CE, "vupklpx v4, v16");
}
#[test]
fn test_vec_vupklsb() {
assert_asm!(0x11407A8E, "vupklsb v10, v15");
}
#[test]
fn test_vec_vupklsh() {
assert_asm!(0x1180C2CE, "vupklsh v12, v24");
}
#[test]
fn test_vec_vxor() {
assert_asm!(0x10654CC4, "vxor v3, v5, v9");
}

View File

@ -1,11 +1,13 @@
use ppc750cl::{Argument, Ins, InsIter, Opcode, FPR, GPR};
use powerpc::{Argument, Extensions, Ins, InsIter, Opcode, GPR};
const EXTENSIONS: Extensions = Extensions::none();
macro_rules! assert_asm {
($ins:ident, $disasm:literal) => {{
assert_eq!(format!("{}", $ins.simplified()), $disasm)
}};
($code:literal, $disasm:literal) => {{
let ins = Ins::new($code);
let ins = Ins::new($code, EXTENSIONS);
assert_eq!(format!("{}", ins.simplified()), $disasm)
}};
}
@ -15,7 +17,7 @@ macro_rules! assert_basic {
assert_eq!(format!("{}", $ins.basic_form()), $disasm)
}};
($code:literal, $disasm:literal) => {{
let ins = Ins::new($code);
let ins = Ins::new($code, EXTENSIONS);
assert_eq!(format!("{}", ins.basic()), $disasm)
}};
}
@ -30,7 +32,7 @@ fn test_ins_add() {
#[test]
fn test_ins_addc() {
let ins = Ins::new(0x7c002014);
let ins = Ins::new(0x7c002014, EXTENSIONS);
assert_eq!(ins.op, Opcode::Addc);
// assert_eq!(ins.fields(), vec![rD(GPR(0)), rA(GPR(0)), rB(GPR(4))]);
assert_asm!(ins, "addc r0, r0, r4");
@ -42,7 +44,7 @@ fn test_ins_addc() {
#[test]
fn test_ins_addi() {
let ins = Ins::new(0x38010140);
let ins = Ins::new(0x38010140, EXTENSIONS);
assert_eq!(ins.op, Opcode::Addi);
// assert_eq!(
// ins.fields(),
@ -205,6 +207,7 @@ fn test_ins_cmpi() {
#[test]
fn test_ins_cmpl() {
assert_asm!(0x7C9A2040, "cmplw cr1, r26, r4");
assert_asm!(0x7f295840, "cmpld cr6, r9, r11");
}
#[test]
@ -285,11 +288,6 @@ fn test_ins_dcbz() {
assert_asm!(0x7C001FEC, "dcbz r0, r3");
}
#[test]
fn test_ins_dcbz_l() {
assert_asm!(0x10061FEC, "dcbz_l r6, r3");
}
#[test]
fn test_ins_divw() {
assert_asm!(0x7C8073D6, "divw r4, r0, r14");
@ -715,183 +713,6 @@ fn test_ins_oris() {
assert_asm!(0x67A06800, "oris r0, r29, 0x6800");
}
#[test]
fn test_ins_psq_l() {
assert_asm!(0xE02500AC, "psq_l f1, 0xac(r5), 0, qr0");
}
#[test]
fn test_ins_psq_lu() {
assert_asm!(0xE5435010, "psq_lu f10, 0x10(r3), 0, qr5");
}
#[test]
fn test_ins_psq_lx() {
let ins = Ins::new(0x1000000C);
assert_eq!(ins.op, Opcode::PsqLx);
// assert_eq!(
// ins.fields(),
// vec![
// frD(FPR(0)),
// rA(GPR(0)),
// rB(GPR(0)),
// ps_WX(OpaqueU(0)),
// ps_IX(GQR(0)),
// ]
// );
assert_eq!(
ins.defs(),
[Argument::FPR(FPR(0)), Argument::None, Argument::None, Argument::None, Argument::None]
);
assert_eq!(
ins.uses(),
[Argument::None, Argument::GPR(GPR(0)), Argument::None, Argument::None, Argument::None]
);
assert_asm!(0x1000000C, "psq_lx f0, r0, r0, 0, qr0");
}
#[test]
fn test_ins_psq_st() {
assert_asm!(0xF1230210, "psq_st f9, 0x210(r3), 0, qr0");
assert_asm!(0xF1238008, "psq_st f9, 0x8(r3), 1, qr0");
}
#[test]
fn test_ins_psq_stu() {
assert_asm!(0xF40A0020, "psq_stu f0, 0x20(r10), 0, qr0");
}
#[test]
fn test_ins_psq_stx() {
assert_asm!(0x13E1000E, "psq_stx f31, r1, r0, 0, qr0");
}
#[test]
fn test_ins_ps_abs() {
assert_asm!(0x10A03210, "ps_abs f5, f6");
}
#[test]
fn test_ins_ps_add() {
assert_asm!(0x1006382A, "ps_add f0, f6, f7");
}
#[test]
fn test_ins_ps_cmpo0() {
assert_asm!(0x10070840, "ps_cmpo0 cr0, f7, f1");
}
#[test]
fn test_ins_ps_cmpu0() {
assert_asm!(0x10003000, "ps_cmpu0 cr0, f0, f6");
}
#[test]
fn test_ins_ps_cmpu1() {
assert_asm!(0x10003080, "ps_cmpu1 cr0, f0, f6");
}
#[test]
fn test_ins_ps_madd() {
assert_asm!(0x112141FA, "ps_madd f9, f1, f7, f8");
}
#[test]
fn test_ins_ps_madds0() {
assert_asm!(0x10AC299C, "ps_madds0 f5, f12, f6, f5");
}
#[test]
fn test_ins_ps_madds1() {
assert_asm!(0x110640DE, "ps_madds1 f8, f6, f3, f8");
}
#[test]
fn test_ins_ps_merge00() {
assert_asm!(0x10400420, "ps_merge00 f2, f0, f0");
}
#[test]
fn test_ins_ps_merge01() {
assert_asm!(0x10400C60, "ps_merge01 f2, f0, f1");
}
#[test]
fn test_ins_ps_merge10() {
assert_asm!(0x104004A0, "ps_merge10 f2, f0, f0");
}
#[test]
fn test_ins_ps_merge11() {
assert_asm!(0x10AA14E0, "ps_merge11 f5, f10, f2");
}
#[test]
fn test_ins_ps_mr() {
assert_asm!(0x10200090, "ps_mr f1, f0");
}
#[test]
fn test_ins_ps_msub() {
assert_asm!(0x10A53778, "ps_msub f5, f5, f29, f6");
}
#[test]
fn test_ins_ps_mul() {
assert_asm!(0x10000032, "ps_mul f0, f0, f0");
}
#[test]
fn test_ins_ps_muls0() {
assert_asm!(0x100002D8, "ps_muls0 f0, f0, f11");
}
#[test]
fn test_ins_ps_muls1() {
assert_asm!(0x10A2005A, "ps_muls1 f5, f2, f1");
}
#[test]
fn test_ins_ps_nabs() {
assert_asm!(0x10803210, "ps_abs f4, f6");
}
#[test]
fn test_ins_ps_neg() {
assert_asm!(0x10E03850, "ps_neg f7, f7");
}
#[test]
fn test_ins_ps_nmadd() {
assert_asm!(0x10CB30FE, "ps_nmadd f6, f11, f3, f6");
}
#[test]
fn test_ins_ps_nmsub() {
assert_asm!(0x107E083C, "ps_nmsub f3, f30, f0, f1");
}
#[test]
fn test_ins_ps_sel() {
assert_asm!(0x106428EE, "ps_sel f3, f4, f3, f5");
}
#[test]
fn test_ins_ps_sub() {
assert_asm!(0x10A92828, "ps_sub f5, f9, f5");
}
#[test]
fn test_ins_ps_sum0() {
assert_asm!(0x10230854, "ps_sum0 f1, f3, f1, f1");
}
#[test]
fn test_ins_ps_sum1() {
assert_asm!(0x10A12956, "ps_sum1 f5, f1, f5, f5");
}
#[test]
fn test_ins_rfi() {
assert_asm!(0x4C000064, "rfi");
@ -1093,7 +914,12 @@ fn test_ins_subfze() {
#[test]
fn test_ins_sync() {
assert_asm!(0x7C0004AC, "sync");
assert_basic!(0x7c0004ac, "sync 0");
assert_basic!(0x7c2004ac, "sync 1");
assert_basic!(0x7c4004ac, "sync 2");
assert_asm!(0x7c0004ac, "sync");
assert_asm!(0x7c2004ac, "lwsync");
assert_asm!(0x7c4004Ac, "ptesync");
}
#[test]
@ -1140,8 +966,9 @@ fn test_ins_xoris() {
#[test]
fn test_ins_iter() {
let mut iter = InsIter::new(&[0x7C, 0x43, 0x22, 0x14, 0x7E, 0x1A, 0x02, 0xA6, 0xFF], 0);
assert_eq!(iter.next(), Some((0, Ins::new(0x7C432214))));
assert_eq!(iter.next(), Some((4, Ins::new(0x7E1A02A6))));
let mut iter =
InsIter::new(&[0x7C, 0x43, 0x22, 0x14, 0x7E, 0x1A, 0x02, 0xA6, 0xFF], 0, EXTENSIONS);
assert_eq!(iter.next(), Some((0, Ins::new(0x7C432214, EXTENSIONS))));
assert_eq!(iter.next(), Some((4, Ins::new(0x7E1A02A6, EXTENSIONS))));
assert_eq!(iter.next(), None);
}

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

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

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

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

427
disasm/tests/test_vmx.rs Normal file
View File

@ -0,0 +1,427 @@
use powerpc::{Extension, Extensions, Ins};
const EXTENSIONS: Extensions = Extensions::from_extension(Extension::Vmx128);
macro_rules! assert_asm {
($ins:ident, $disasm:literal) => {{
assert_eq!(format!("{}", $ins.simplified()), $disasm)
}};
($code:literal, $disasm:literal) => {{
let ins = Ins::new($code, EXTENSIONS);
assert_eq!(format!("{}", ins.simplified()), $disasm)
}};
}
#[test]
fn test_vmx_enables_altivec() {
let extensions = Extensions::from_extension(Extension::Vmx128);
assert!(extensions.contains(Extension::AltiVec));
}
#[test]
fn test_extensions_remove_vmx() {
let mut extensions = Extensions::from_extension(Extension::Vmx128);
extensions.remove(Extension::Vmx128);
assert!(!extensions.contains(Extension::Vmx128));
// Ensure AltiVec is still enabled
assert!(extensions.contains(Extension::AltiVec));
}
#[test]
fn test_extensions_remove_altivec() {
let mut extensions = Extensions::from_extension(Extension::Vmx128);
extensions.remove(Extension::AltiVec);
assert!(!extensions.contains(Extension::AltiVec));
// Ensure Vmx128 is disabled (AltiVec required)
assert!(!extensions.contains(Extension::Vmx128));
}
#[test]
fn test_vmx_lvewx128() {
assert_asm!(0x1243388F, "lvewx128 v114, r3, r7");
}
#[test]
fn test_vmx_lvlx128() {
assert_asm!(0x1085440F, "lvlx128 v100, r5, r8");
}
#[test]
fn test_vmx_lvrx128() {
assert_asm!(0x108EE44B, "lvrx128 v68, r14, r28");
}
#[test]
fn test_vmx_lvlxl128() {
assert_asm!(0x1105FE0B, "lvlxl128 v72, r5, r31");
}
#[test]
fn test_vmx_lvrxl128() {
assert_asm!(0x12A01E4B, "lvrxl128 v85, r0, r3");
}
#[test]
fn test_vmx_lvsl128() {
assert_asm!(0x138AF00B, "lvsl128 v92, r10, r30");
}
#[test]
fn test_vmx_lvsr128() {
assert_asm!(0x1016C04F, "lvsr128 v96, r22, r24");
}
#[test]
fn test_vmx_lvx128() {
assert_asm!(0x120938CF, "lvx128 v112, r9, r7");
}
#[test]
fn test_vmx_lvxl128() {
assert_asm!(0x12C322CF, "lvxl128 v118, r3, r4");
}
#[test]
fn test_vmx_stvewx128() {
assert_asm!(0x131BF98F, "stvewx128 v120, r27, r31");
}
#[test]
fn test_vmx_stvlx128() {
assert_asm!(0x12602D0B, "stvlx128 v83, r0, r5");
}
#[test]
fn test_vmx_stvlxl128() {
assert_asm!(0x12C3A70B, "stvlxl128 v86, r3, r20");
}
#[test]
fn test_vmx_stvrx128() {
assert_asm!(0x10B8F54B, "stvrx128 v69, r24, r30");
}
#[test]
fn test_vmx_stvrxl128() {
assert_asm!(0x10C7074B, "stvrxl128 v70, r7, r0");
}
#[test]
fn test_vmx_stvx128() {
assert_asm!(0x130341C3, "stvx128 v24, r3, r8");
}
#[test]
fn test_vmx_stvxl128() {
assert_asm!(0x13E553C3, "stvxl128 v31, r5, r10");
}
#[test]
fn test_vmx_vaddfp128() {
assert_asm!(0x151E301B, "vaddfp128 v72, v30, v102");
}
#[test]
fn test_vmx_vand128() {
assert_asm!(0x16900E12, "vand128 v20, v80, v65");
}
#[test]
fn test_vmx_vandc128() {
assert_asm!(0x15EBFE52, "vandc128 v15, v75, v95");
}
#[test]
fn test_vmx_vctsxs128() {
assert_asm!(0x1A42D23B, "vctsxs128 v82, v122, 0x2");
}
#[test]
fn test_vmx_vctuxs128() {
assert_asm!(0x1BEACA78, "vctuxs128 v95, v25, 0xa");
}
#[test]
fn test_vmx_vcmpbfp128() {
assert_asm!(0x1BA5598E, "vcmpbfp128 v125, v5, v75");
assert_asm!(0x198D79C2, "vcmpbfp128. v12, v13, v79");
}
#[test]
fn test_vmx_vcmpeqfp128() {
assert_asm!(0x1800D80B, "vcmpeqfp128 v64, v0, v123");
assert_asm!(0x1ACD1C43, "vcmpeqfp128. v22, v77, v99");
}
#[test]
fn test_vmx_vcmpequw128() {
assert_asm!(0x18D0D60A, "vcmpequw128 v70, v80, v90");
assert_asm!(0x18800A40, "vcmpequw128. v4, v0, v1");
}
#[test]
fn test_vmx_vcmpgefp128() {
assert_asm!(0x1A8A1483, "vcmpgefp128 v20, v74, v98");
assert_asm!(0x18EB7CEF, "vcmpgefp128. v103, v107, v111");
}
#[test]
fn test_vmx_vcmpgtfp128() {
assert_asm!(0x1BD48102, "vcmpgtfp128 v30, v20, v80");
assert_asm!(0x1B586D68, "vcmpgtfp128. v90, v120, v13");
}
#[test]
fn test_vmx_vcfsx128() {
assert_asm!(0x18749ABC, "vcfsx128 v99, v19, -0xc");
}
#[test]
fn test_vmx_vcfux128() {
assert_asm!(0x1A6D1AF8, "vcfux128 v83, v3, 0xd");
}
#[test]
fn test_vmx_vexptefp128() {
assert_asm!(0x198056B0, "vexptefp128 v12, v10");
}
#[test]
fn test_vmx_vlogefp128() {
assert_asm!(0x1900FEFB, "vlogefp128 v72, v127");
}
#[test]
fn test_vmx_vmaddcfp128() {
assert_asm!(0x163B1912, "vmaddcfp128 v17, v27, v67");
}
#[test]
fn test_vmx_vmaddfp128() {
assert_asm!(0x16B3ECFB, "vmaddfp128 v85, v115, v125");
}
#[test]
fn test_vmx_vmaxfp128() {
assert_asm!(0x1B274683, "vmaxfp128 v25, v71, v104");
}
#[test]
fn test_vmx_vminfp128() {
assert_asm!(0x1BE012C0, "vminfp128 v31, v0, v2");
}
#[test]
fn test_vmx_vmrghw128() {
assert_asm!(0x18CA730B, "vmrghw128 v70, v10, v110");
}
#[test]
fn test_vmx_vmrglw128() {
assert_asm!(0x1BD2D743, "vmrglw128 v30, v82, v122");
}
#[test]
fn test_vmx_vmsum3fp128() {
assert_asm!(0x14FBF993, "vmsum3fp128 v7, v27, v127");
}
#[test]
fn test_vmx_vmsum4fp128() {
assert_asm!(0x14A869D0, "vmsum4fp128 v5, v8, v13");
}
#[test]
fn test_vmx_vmulfp128() {
assert_asm!(0x1498DCBF, "vmulfp128 v100, v120, v123");
}
#[test]
fn test_vmx_vnmsubfp128() {
assert_asm!(0x17DBCD53, "vnmsubfp128 v30, v91, v121");
}
#[test]
fn test_vmx_vnor128() {
assert_asm!(0x176A6290, "vnor128 v27, v10, v12");
}
#[test]
fn test_vmx_vor128() {
assert_asm!(0x17EC3ADC, "vor128 v127, v12, v7");
}
#[test]
fn test_vmx_vperm128() {
assert_asm!(0x1661158F, "vperm128 v115, v65, v98, v6");
}
#[test]
fn test_vmx_vpermwi128() {
assert_asm!(0x19C342DE, "vpermwi128 v110, v72, 99");
}
#[test]
fn test_vmx_vpkd3d128() {
assert_asm!(0x1935DEDC, "vpkd3d128 v105, v27, 5, 1, 3");
}
#[test]
fn test_vmx_vpkshss128() {
assert_asm!(0x16C0F62B, "vpkshss128 v86, v96, v126");
}
#[test]
fn test_vmx_vpkshus128() {
assert_asm!(0x153D6E48, "vpkshus128 v73, v93, v13");
}
#[test]
fn test_vmx_vpkswss128() {
assert_asm!(0x16FE7280, "vpkswss128 v23, v30, v14");
}
#[test]
fn test_vmx_vpkswus128() {
assert_asm!(0x161836C3, "vpkswus128 v16, v88, v102");
}
#[test]
fn test_vmx_vpkuhum128() {
assert_asm!(0x14E3BF02, "vpkuhum128 v7, v67, v87");
}
#[test]
fn test_vmx_vpkuhus128() {
assert_asm!(0x1600A348, "vpkuhus128 v80, v0, v20");
}
#[test]
fn test_vmx_vpkuwum128() {
assert_asm!(0x16EACB83, "vpkuwum128 v23, v10, v121");
}
#[test]
fn test_vmx_vpkuwus128() {
assert_asm!(0x17E72FC3, "vpkuwus128 v31, v71, v101");
}
#[test]
fn test_vmx_vrefp128() {
assert_asm!(0x1800F638, "vrefp128 v64, v30");
}
#[test]
fn test_vmx_vrfim128() {
assert_asm!(0x18802B30, "vrfim128 v4, v5");
}
#[test]
fn test_vmx_vrfin128() {
assert_asm!(0x1A200B73, "vrfin128 v17, v97");
}
#[test]
fn test_vmx_vrfip128() {
assert_asm!(0x1B605BB2, "vrfip128 v27, v75");
}
#[test]
fn test_vmx_vrfiz128() {
assert_asm!(0x1A8053F0, "vrfiz128 v20, v10");
}
#[test]
fn test_vmx_vrlimi128() {
assert_asm!(0x18796798, "vrlimi128 v67, v12, 0x19, 2")
}
#[test]
fn test_vmx_vrlw128() {
assert_asm!(0x1B002050, "vrlw128 v24, v0, v4");
}
#[test]
fn test_vmx_vrsqrtefp128() {
assert_asm!(0x19800673, "vrsqrtefp128 v12, v96");
}
#[test]
fn test_vmx_vsel128() {
assert_asm!(0x146CDF5A, "vsel128 v67, v76, v91");
}
#[test]
fn test_vmx_vsldoi128() {
assert_asm!(0x130BFF30, "vsldoi128 v24, v107, v31, 12");
}
#[test]
fn test_vmx_vslo128() {
assert_asm!(0x14E08B90, "vslo128 v7, v0, v17");
}
#[test]
fn test_vmx_vslw128() {
assert_asm!(0x1A1AC0D2, "vslw128 v16, v26, v88");
}
#[test]
fn test_vmx_vspltisw128() {
assert_asm!(0x1B68A772, "vspltisw128 v27, v84, 0x8");
}
#[test]
fn test_vmx_vspltw128() {
assert_asm!(0x1996EF32, "vspltw128 v12, v93, 0x16");
}
#[test]
fn test_vmx_vsraw128() {
assert_asm!(0x19B71950, "vsraw128 v13, v23, v3");
}
#[test]
fn test_vmx_vsro128() {
assert_asm!(0x17C3E3D3, "vsro128 v30, v3, v124");
}
#[test]
fn test_vmx_vsrw128() {
assert_asm!(0x1B9271D3, "vsrw128 v28, v18, v110");
}
#[test]
fn test_vmx_vsubfp128() {
assert_asm!(0x17692C50, "vsubfp128 v27, v73, v5");
}
#[test]
fn test_vmx_vupkd3d128() {
assert_asm!(0x19FECFF0, "vupkd3d128 v15, v25, 0x1e");
}
#[test]
fn test_vmx_vupkhsb128() {
assert_asm!(0x1B60FB83, "vupkhsb128 v27, v127");
}
#[test]
fn test_vmx_vupkhsh128() {
assert_asm!(0x186017a0, "vupkhsh128 v3, v2");
}
#[test]
fn test_vmx_vupklsb128() {
assert_asm!(0x1A00A3C3, "vupklsb128 v16, v116");
}
#[test]
fn test_vmx_vupklsh128() {
assert_asm!(0x186017e0, "vupklsh128 v3, v2");
}
#[test]
fn test_vmx_vxor128() {
assert_asm!(0x17E3EF32, "vxor128 v31, v99, v93");
}

View File

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

View File

@ -1,3 +1,4 @@
use powerpc::Extensions;
use std::io::Write;
use std::ops::Range;
use std::sync::atomic::{AtomicU32, Ordering};
@ -5,9 +6,9 @@ use std::sync::Arc;
use std::time::{Duration, Instant};
fn main() {
let matches = clap::Command::new("ppc750cl-fuzz")
.version("0.2.0")
.about("Complete \"fuzzer\" for ppc750cl disassembler")
let matches = clap::Command::new("powerpc-fuzz")
.version(env!("CARGO_PKG_VERSION"))
.about("Complete \"fuzzer\" for powerpc disassembler")
.arg(
clap::Arg::new("threads")
.short('t')
@ -65,7 +66,7 @@ impl MultiFuzzer {
last = now;
let progress = 100f32 * ((now as f32) / (0x1_0000_0000u64 as f32));
let avg = now as f32 / elapsed.as_secs_f32() / this.threads.len() as f32;
println!("{}/s\t{:05.2}%\tn=0x{:08x} (avg {}/s)", per_second, progress, now, avg);
println!("{per_second}/s\t{progress:05.2}%\tn=0x{now:08x} (avg {avg}/s)");
}
});
}
@ -101,10 +102,11 @@ impl Fuzzer {
let counter = Arc::clone(&self.counter);
let range = self.range.clone();
std::thread::spawn(move || {
let mut parsed = ppc750cl::ParsedIns::default();
let mut parsed = powerpc::ParsedIns::default();
for x in range.clone() {
ppc750cl::Ins::new(x).parse_simplified(&mut parsed);
writeln!(&mut devnull, "{}", parsed).unwrap();
powerpc::Ins::new(x, Extensions::from_bitmask(u32::MAX))
.parse_simplified(&mut parsed);
writeln!(&mut devnull, "{parsed}").unwrap();
if x % (1 << 19) == 0 {
counter.store(x, Ordering::Relaxed);
}

View File

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

View File

@ -8,21 +8,36 @@ use proc_macro2::{Literal, TokenStream};
use quote::{format_ident, quote};
use std::collections::BTreeMap;
enum OpcodeOrMnemonic {
Opcode(Opcode),
Mnemonic(Mnemonic),
}
impl OpcodeOrMnemonic {
fn name(&self) -> &str {
match self {
OpcodeOrMnemonic::Opcode(opcode) => &opcode.name,
OpcodeOrMnemonic::Mnemonic(mnemonic) => &mnemonic.name,
}
}
}
pub fn gen_asm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
let mut functions = TokenStream::new();
let mut func_map = phf_codegen::Map::new();
let mut mnemonic_map = BTreeMap::<String, Vec<OpcodeOrMnemonic>>::new();
for opcode in &isa.opcodes {
let name = format_ident!("gen_{}", opcode.ident());
let inner = gen_opcode(opcode, isa)?;
functions.extend(quote! {
fn #name(args: &Arguments, modifiers: u32) -> Result<u32, ArgumentError> { #inner }
});
mnemonic_map
.entry(opcode.name.clone())
.or_default()
.push(OpcodeOrMnemonic::Opcode(opcode.clone()));
}
let mut mnemonic_map = BTreeMap::<String, Vec<Mnemonic>>::new();
for mnemonic in &isa.mnemonics {
mnemonic_map.entry(mnemonic.name.clone()).or_default().push(mnemonic.clone());
mnemonic_map
.entry(mnemonic.name.clone())
.or_default()
.push(OpcodeOrMnemonic::Mnemonic(mnemonic.clone()));
}
for (name, mnemonics) in &mnemonic_map {
let fn_name = format!("gen_{}", to_ident(name));
@ -32,12 +47,19 @@ pub fn gen_asm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
inner = TokenStream::new();
let mut max_args = 0;
for mnemonic in mnemonics {
let gen = gen_mnemonic(mnemonic, isa, false)?;
let arg_n = Literal::usize_unsuffixed(mnemonic.args.len());
let (gen, args_len) = match mnemonic {
OpcodeOrMnemonic::Opcode(opcode) => {
(gen_opcode(opcode, isa, false)?, opcode.args.len())
}
OpcodeOrMnemonic::Mnemonic(mnemonic) => {
(gen_mnemonic(mnemonic, isa, false)?, mnemonic.args.len())
}
};
let arg_n = Literal::usize_unsuffixed(args_len);
inner.extend(quote! {
#arg_n => { #gen }
});
max_args = max_args.max(mnemonic.args.len());
max_args = max_args.max(args_len);
}
let max_args = Literal::usize_unsuffixed(max_args);
inner.extend(quote! {
@ -45,31 +67,25 @@ pub fn gen_asm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
});
inner = quote! { match arg_count(args) { #inner } };
} else {
inner = gen_mnemonic(mnemonics.first().unwrap(), isa, true)?;
inner = match mnemonics.first().unwrap() {
OpcodeOrMnemonic::Opcode(opcode) => gen_opcode(opcode, isa, true)?,
OpcodeOrMnemonic::Mnemonic(mnemonic) => gen_mnemonic(mnemonic, isa, true)?,
};
}
functions.extend(quote! {
fn #fn_ident(args: &Arguments, modifiers: u32) -> Result<u32, ArgumentError> { #inner }
});
}
for (opcode, modifiers) in isa.opcodes.iter().flat_map(|o| {
modifiers_iter(&o.modifiers, isa).filter(|m| modifiers_valid(m)).map(move |m| (o, m))
}) {
let suffix = modifiers.iter().map(|m| m.suffix).collect::<String>();
let mut pattern = 0;
for modifier in &modifiers {
pattern |= modifier.mask();
}
func_map.entry(
format!("{}{}", opcode.name, suffix),
&format!("(gen_{}, {:#x})", opcode.ident(), pattern),
);
}
for (mnemonic, modifiers) in mnemonic_map.iter().flat_map(|(_, mnemonics)| {
let mnemonic = mnemonics.first().unwrap();
let modifiers = match mnemonic {
OpcodeOrMnemonic::Opcode(opcode) => &opcode.modifiers,
OpcodeOrMnemonic::Mnemonic(mnemonic) => {
let opcode = isa.find_opcode(&mnemonic.opcode).unwrap();
let modifiers = mnemonic.modifiers.as_deref().unwrap_or(&opcode.modifiers);
mnemonic.modifiers.as_deref().unwrap_or(&opcode.modifiers)
}
};
modifiers_iter(modifiers, isa).filter(|m| modifiers_valid(m)).map(move |m| (mnemonic, m))
}) {
let suffix = modifiers.iter().map(|m| m.suffix).collect::<String>();
@ -77,10 +93,8 @@ pub fn gen_asm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
for modifier in &modifiers {
pattern |= modifier.mask();
}
func_map.entry(
format!("{}{}", mnemonic.name, suffix),
&format!("(gen_{}, {:#x})", to_ident(&mnemonic.name), pattern),
);
let name = format!("{}{}", mnemonic.name(), suffix);
func_map.entry(name, format!("(gen_{}, {:#x})", to_ident(mnemonic.name()), pattern));
}
let func_map = syn::parse_str::<TokenStream>(&func_map.build().to_string())?;
@ -88,7 +102,7 @@ pub fn gen_asm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
Ok(quote! {
#![allow(unused)]
#![cfg_attr(rustfmt, rustfmt_skip)]
#[comment = " Code generated by ppc750-genisa. DO NOT EDIT."]
#[comment = " Code generated by powerpc-genisa. DO NOT EDIT."]
use crate::types::*;
pub type Arguments = [Argument; #max_args];
#functions
@ -105,7 +119,7 @@ pub fn gen_asm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
}
fn gen_parse_field(field: &Field, i: usize) -> Result<(TokenStream, bool)> {
let Some(bits) = field.bits else { bail!("Field {} has no bits", field.name) };
let Some(bits) = &field.bits else { bail!("Field {} has no bits", field.name) };
let i = Literal::usize_unsuffixed(i);
Ok(if field.signed {
let max_value = 1 << (bits.len() - 1 + field.shift_left);
@ -119,89 +133,84 @@ fn gen_parse_field(field: &Field, i: usize) -> Result<(TokenStream, bool)> {
})
}
fn gen_field(
field: &Field,
mut accessor: TokenStream,
finalize: fn(TokenStream) -> TokenStream,
signed: bool,
) -> Result<TokenStream> {
let Some(bits) = field.bits else { bail!("Field {} has no bits", field.name) };
let mut shift_right = bits.shift();
let mut shift_left = field.shift_left;
if shift_right == shift_left {
// Optimization: these cancel each other out
// Adjust subsequent operations to operate on the full value
shift_right = 0;
shift_left = 0;
}
fn gen_field(field: &Field, mut accessor: TokenStream, signed: bool) -> Result<TokenStream> {
let Some(bits) = &field.bits else { bail!("Field {} has no bits", field.name) };
// Optimization: offset all shifts to avoid unnecessary operations
let shift_offset = field.shift_left.min(bits.shift());
// Handle the operations (in reverse order from disassembly)
let mut operations = TokenStream::new();
let mut inner;
if signed {
accessor = quote! { #accessor as u32 };
}
// Swap 5-bit halves (SPR, TBR)
if field.split {
operations.extend(quote! {
value = ((value & 0b11111_00000) >> 5) | ((value & 0b00000_11111) << 5);
});
inner = quote! { value };
} else {
inner = accessor.clone();
}
let mut bit_position = 0;
for range in bits.0.iter().rev() {
let mut shift_right = range.shift() - shift_offset;
let mut shift_left = field.shift_left + bit_position - shift_offset;
// Handle left shift
if shift_left > 0 {
// Optimize shifts
let common_shift = shift_right.min(shift_left);
shift_right -= common_shift;
shift_left -= common_shift;
// Shift left
let mut inner = if shift_left > 0 {
let shift_left = Literal::u8_unsuffixed(shift_left);
inner = quote! { (#inner >> #shift_left) };
}
quote! { (arg >> #shift_left) }
} else {
quote! { arg }
};
// Mask
let mask = HexLiteral(bits.mask() >> shift_right);
let mask = HexLiteral(range.mask() >> shift_right);
inner = quote! { #inner & #mask };
// Shift right
if shift_right > 0 {
let shift = Literal::u8_unsuffixed(shift_right);
inner = quote! { (#inner) << #shift };
let shift_right = Literal::u8_unsuffixed(shift_right);
inner = quote! { (#inner) << #shift_right };
}
bit_position += range.len();
operations.extend(quote! {
code |= #inner;
});
}
if operations.is_empty() {
Ok(finalize(inner))
} else {
inner = finalize(inner);
Ok(quote! {{
let mut value = #accessor;
let arg = #accessor;
#operations
#inner
}})
}
}
fn gen_opcode(opcode: &Opcode, isa: &Isa) -> Result<TokenStream> {
fn gen_opcode(opcode: &Opcode, isa: &Isa, check_arg_count: bool) -> Result<TokenStream> {
let mut args = TokenStream::new();
for (i, arg) in opcode.args.iter().enumerate() {
let field = isa.find_field(arg).unwrap();
let comment = format!(" {}", field.name);
let (accessor, signed) = gen_parse_field(field, i)?;
let value = gen_field(field, accessor, |s| s, signed)?;
let operations = gen_field(field, accessor, signed)?;
args.extend(quote! {
#[comment = #comment]
code |= #value;
#operations
});
}
let arg_count = Literal::usize_unsuffixed(opcode.args.len());
let mut result = TokenStream::new();
if check_arg_count {
result.extend(quote! { check_arg_count(args, #arg_count)?; });
}
let pattern = HexLiteral(opcode.pattern);
Ok(quote! {
check_arg_count(args, #arg_count)?;
result.extend(quote! {
let mut code = #pattern | modifiers;
#args
Ok(code)
})
});
Ok(result)
}
fn gen_mnemonic(mnemonic: &Mnemonic, isa: &Isa, check_arg_count: bool) -> Result<TokenStream> {
@ -210,11 +219,11 @@ fn gen_mnemonic(mnemonic: &Mnemonic, isa: &Isa, check_arg_count: bool) -> Result
};
let mut args = TokenStream::new();
for (i, arg) in mnemonic.args.iter().enumerate() {
let comment = format!(" {}", arg);
let arg = gen_argument(&mnemonic.args, i, isa, mnemonic.replace_assemble.get(arg))?;
let comment = format!(" {arg}");
let operations = gen_argument(&mnemonic.args, i, isa, mnemonic.replace_assemble.get(arg))?;
args.extend(quote! {
#[comment = #comment]
code |= #arg;
#operations
});
}
@ -238,10 +247,10 @@ fn gen_mnemonic(mnemonic: &Mnemonic, isa: &Isa, check_arg_count: bool) -> Result
format!("Mnemonic {}: unknown field {}", mnemonic.name, in_field.name)
})?;
let (accessor, signed) = gen_parse_field(in_field, arg_n)?;
let arg = gen_field(condition.field, accessor, |s| s, signed)?;
let operations = gen_field(condition.field, accessor, signed)?;
args.extend(quote! {
#[comment = #comment]
code |= #arg;
#operations
});
}
ConditionValue::Complex(c) => {
@ -256,10 +265,10 @@ fn gen_mnemonic(mnemonic: &Mnemonic, isa: &Isa, check_arg_count: bool) -> Result
any_signed |= signed;
Ok(s)
})?;
let arg = gen_field(condition.field, quote! { (#arg) }, |s| s, any_signed)?;
let operations = gen_field(condition.field, quote! { (#arg) }, any_signed)?;
args.extend(quote! {
#[comment = #comment]
code |= #arg;
#operations
});
}
}
@ -299,9 +308,9 @@ fn gen_argument(
any_signed |= signed;
Ok(parse)
})?;
gen_field(field, quote! { (#stream) }, |s| s, any_signed)
gen_field(field, quote! { (#stream) }, any_signed)
} else {
let (accessor, signed) = gen_parse_field(field, arg_n)?;
gen_field(field, accessor, |s| s, signed)
gen_field(field, accessor, signed)
}
}

View File

@ -64,7 +64,7 @@ pub fn parse_conditions<'a>(condition: &'a str, isa: &'a Isa) -> Result<Vec<Cond
} else if let Some((field, value)) = tok.split_once(" >= ") {
(field, value, ConditionOp::Gte)
} else {
log::error!("Invalid condition: {}", tok);
log::error!("Invalid condition: {tok}");
continue;
};
let mut field_mask = u32::MAX;
@ -74,7 +74,7 @@ pub fn parse_conditions<'a>(condition: &'a str, isa: &'a Isa) -> Result<Vec<Cond
};
let field = isa
.find_field(field)
.with_context(|| format!("Condition references unknown field {}", field))?;
.with_context(|| format!("Condition references unknown field {field}"))?;
let value = if let Ok(value) = parse_unsigned(value) {
ConditionValue::ConstantUnsigned(value)
} else if let Ok(value) = parse_signed(value) {

View File

@ -1,27 +1,31 @@
use crate::condition::{parse_conditions, replace_fields};
use crate::ident;
use crate::isa::{modifiers_iter, modifiers_valid, HexLiteral, Isa, Opcode};
use crate::{
condition::{parse_conditions, replace_fields},
ident,
isa::{modifiers_iter, modifiers_valid, HexLiteral, Isa, Opcode},
};
use anyhow::{bail, ensure, Result};
use proc_macro2::{Literal, TokenStream};
use proc_macro2::{Ident, Literal, TokenStream};
use quote::{format_ident, quote};
use std::collections::HashMap;
use std::num::NonZeroU8;
use std::{
collections::{btree_map, BTreeMap, HashMap},
hash::{DefaultHasher, Hash, Hasher},
};
pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
// The entry table allows us to quickly find the range of possible opcodes
// for a given 6-bit prefix. 2*64 bytes should fit in a cache line (or two).
struct OpcodeEntry {
start: u8,
count: u8,
start: u16,
count: u16,
}
let mut sorted_ops = Vec::<Opcode>::new();
let mut entries = Vec::<OpcodeEntry>::new();
let mut sorted_ops = Vec::<Opcode>::with_capacity(isa.opcodes.len());
let mut entries = Vec::<OpcodeEntry>::with_capacity(64);
for i in 0..64 {
let mut entry = OpcodeEntry { start: 0, count: 0 };
for opcode in &isa.opcodes {
if (opcode.pattern >> 26) as u8 == i {
if (opcode.pattern >> 26) as u16 == i {
if entry.count == 0 {
entry.start = sorted_ops.len() as u8;
entry.start = sorted_ops.len() as u16;
}
// Sanity check for duplicate opcodes
if sorted_ops.iter().any(|op| op.name == opcode.name) {
@ -36,19 +40,18 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
} else if let Some(op) = (entry.count == 1).then(|| &sorted_ops[entry.start as usize]) {
log::info!("{:#X}: {}", i, op.name);
} else {
log::info!("{:#X}: <invalid>", i);
log::info!("{i:#X}: <invalid>");
}
entries.push(entry);
}
ensure!(sorted_ops.len() == isa.opcodes.len());
ensure!(sorted_ops.len() <= 255);
let opcode_max = Literal::u8_unsuffixed((sorted_ops.len() - 1) as u8);
let opcode_max = Literal::u16_unsuffixed((sorted_ops.len() - 1) as u16);
// Generate the opcode entries table
let mut opcode_entries = TokenStream::new();
for entry in &entries {
let start = Literal::u8_unsuffixed(entry.start);
let end = Literal::u8_unsuffixed(entry.start + entry.count);
let start = Literal::u16_unsuffixed(entry.start);
let end = Literal::u16_unsuffixed(entry.start + entry.count);
opcode_entries.extend(quote! { (#start, #end), });
}
@ -59,9 +62,21 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
for (idx, opcode) in sorted_ops.iter().enumerate() {
let bitmask = HexLiteral(opcode.mask(isa));
let pattern = HexLiteral(opcode.pattern);
let enum_idx = Literal::u8_unsuffixed(idx as u8);
let enum_idx = Literal::u16_unsuffixed(idx as u16);
let name = &opcode.name;
opcode_patterns.extend(quote! { (#bitmask, #pattern), });
let comment = format!(" {name}");
let extension =
isa.extensions.iter().find(|(_, e)| e.opcodes.iter().any(|o| o.name == opcode.name));
let initializer = if let Some((id, _)) = extension {
let ident = format_ident!("{id}");
quote! { OpcodePattern::extension(#bitmask, #pattern, Extension::#ident) }
} else {
quote! { OpcodePattern::base(#bitmask, #pattern) }
};
opcode_patterns.extend(quote! {
#[comment = #comment]
#initializer,
});
opcode_names.extend(quote! { #name, });
let doc = opcode.doc();
let variant = opcode.variant();
@ -74,7 +89,7 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
// Generate field and modifier accessors
let mut ins_fields = TokenStream::new();
for field in &isa.fields {
let Some(bits) = field.bits else {
let Some(bits) = &field.bits else {
continue;
};
// TODO get rid of .nz hack
@ -82,56 +97,72 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
continue;
}
let mut shift_right = bits.shift();
let mut shift_left = field.shift_left;
if shift_right == shift_left {
// Optimization: these cancel each other out
// Adjust subsequent operations to operate on the full value
shift_right = 0;
shift_left = 0;
}
// Optimization: offset all shifts to avoid unnecessary operations
let shift_offset = field.shift_left.min(bits.shift());
// Shift right and mask
let mut inner = quote! { self.code };
// Extract the field bits from the instruction
let mut operations = Vec::new();
let mut bit_position = 0;
for range in bits.0.iter().rev() {
let mut shift_right = range.shift() - shift_offset;
let mut shift_left = bit_position;
// Optimize shifts
let common_shift = shift_right.min(shift_left);
shift_right -= common_shift;
shift_left -= common_shift;
// Shift right
let mut result = quote! { self.code };
if shift_right > 0 {
let shift = Literal::u8_unsuffixed(shift_right);
inner = quote! { (#inner >> #shift) };
result = quote! { (#result >> #shift) };
}
let mask = HexLiteral(bits.mask() >> shift_right);
inner = quote! { #inner & #mask };
// Mask
let mask = HexLiteral(range.mask() >> shift_right);
result = quote! { (#result & #mask) };
// Shift left
if shift_left > 0 {
let shift = Literal::u8_unsuffixed(shift_left);
result = quote! { (#result << #shift) };
}
bit_position += range.len();
operations.push(result);
}
let mut inner = if operations.len() > 1 {
quote! { (#(#operations)|*) }
} else {
operations.into_iter().next().unwrap()
};
// Determine the smallest integer type that can hold the value
let num_bits = bits.len() + field.shift_left;
let (out_type, cast, sign_shift) = match (num_bits, field.signed) {
(1..=8, false) => (ident!(u8), true, None),
(9..=16, false) => (ident!(u16), true, None),
(17..=32, false) => (ident!(u32), false, None),
(1..=8, true) => (ident!(i8), true, NonZeroU8::new(8 - num_bits)),
(9..=16, true) => (ident!(i16), true, NonZeroU8::new(16 - num_bits)),
(17..=32, true) => (ident!(i32), true, NonZeroU8::new(32 - num_bits)),
(1..=8, false) => (ident!(u8), true, 0),
(9..=16, false) => (ident!(u16), true, 0),
(17..=32, false) => (ident!(u32), false, 0),
(1..=8, true) => (ident!(i8), true, 8 - bits.len()),
(9..=16, true) => (ident!(i16), true, 16 - bits.len()),
(17..=32, true) => (ident!(i32), true, 32 - bits.len()),
(v, _) => bail!("Unsupported field size {v}"),
};
// Handle sign extension
if let Some(sign_shift) = sign_shift {
let sign_shift = Literal::u8_unsuffixed(sign_shift.get());
inner = quote! { (((#inner) << #sign_shift) as #out_type) >> #sign_shift };
if sign_shift > shift_offset {
let sign_shift = Literal::u8_unsuffixed(sign_shift - shift_offset);
inner = quote! { ((#inner << #sign_shift) as #out_type) >> #sign_shift };
} else if cast {
inner = quote! { (#inner) as #out_type };
inner = quote! { #inner as #out_type };
}
// Handle left shift
let shift_left = field.shift_left - shift_offset;
if shift_left > 0 {
let shift_left = Literal::u8_unsuffixed(shift_left);
inner = quote! { (#inner) << #shift_left };
}
// Swap 5-bit halves (SPR, TBR)
if field.split {
inner = quote! {
let value = #inner;
((value & 0b11111_00000) >> 5) | ((value & 0b00000_11111) << 5)
};
inner = quote! { #inner << #shift_left };
}
// Write the accessor
@ -230,9 +261,12 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
let mut defs_uses_functions = TokenStream::new();
let mut defs_refs = TokenStream::new();
let mut uses_refs = TokenStream::new();
// Deduplicate equivalent functions
let mut hash_to_fn = BTreeMap::<u64, Ident>::new();
for opcode in &sorted_ops {
let mut defs = TokenStream::new();
let mut uses = TokenStream::new();
let mut defs_count = 0;
for def in &opcode.defs {
if isa.find_field(def).is_some_and(|f| f.arg.is_none()) {
@ -242,6 +276,8 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
defs.extend(quote! { #arg, });
defs_count += 1;
}
let mut uses = TokenStream::new();
let mut use_count = 0;
for use_ in &opcode.uses {
if let Some(use_) = use_.strip_suffix(".nz") {
@ -264,10 +300,23 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
defs.extend(quote! { Argument::None, });
}
let defs_name = format_ident!("defs_{}", opcode.ident());
let mut hasher = DefaultHasher::default();
opcode.defs.hash(&mut hasher);
let defs_hash = hasher.finish();
match hash_to_fn.entry(defs_hash) {
btree_map::Entry::Vacant(e) => {
e.insert(defs_name.clone());
defs_uses_functions.extend(quote! {
fn #defs_name(out: &mut Arguments, ins: Ins) { *out = [#defs]; }
});
defs_refs.extend(quote! { #defs_name, });
}
btree_map::Entry::Occupied(e) => {
let ident = e.get();
defs_refs.extend(quote! { #ident, });
}
}
} else {
defs_refs.extend(quote! { defs_uses_empty, });
}
@ -277,10 +326,23 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
uses.extend(quote! { Argument::None, });
}
let uses_name = format_ident!("uses_{}", opcode.ident());
let mut hasher = DefaultHasher::default();
opcode.uses.hash(&mut hasher);
let uses_hash = hasher.finish();
match hash_to_fn.entry(uses_hash) {
btree_map::Entry::Vacant(e) => {
e.insert(uses_name.clone());
defs_uses_functions.extend(quote! {
fn #uses_name(out: &mut Arguments, ins: Ins) { *out = [#uses]; }
});
uses_refs.extend(quote! { #uses_name, });
}
btree_map::Entry::Occupied(e) => {
let ident = e.get();
uses_refs.extend(quote! { #ident, });
}
}
} else {
uses_refs.extend(quote! { defs_uses_empty, });
}
@ -289,78 +351,145 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
fn defs_uses_empty(out: &mut Arguments, _ins: Ins) { *out = EMPTY_ARGS; }
});
// Filling the tables to 256 entries to avoid bounds checks
for _ in sorted_ops.len()..256 {
opcode_patterns.extend(quote! { (0, 0), });
opcode_names.extend(quote! { "<illegal>", });
basic_functions_ref.extend(quote! { mnemonic_illegal, });
simplified_functions_ref.extend(quote! { mnemonic_illegal, });
defs_refs.extend(quote! { defs_uses_empty, });
uses_refs.extend(quote! { defs_uses_empty, });
}
let mut none_args = TokenStream::new();
for _ in 0..max_args {
none_args.extend(quote! { Argument::None, });
}
let extension_variants = isa
.extensions
.iter()
.map(|(id, ext)| {
let ident = format_ident!("{id}");
let desc = format!(" {}", ext.name);
quote! {
#[doc = #desc]
#ident,
}
})
.collect::<Vec<_>>();
let extension_requires = isa
.extensions
.iter()
.filter_map(|(id, ext)| {
if ext.requires.is_empty() {
return None;
}
let requires = ext
.requires
.iter()
.map(|parent| {
let ident = format_ident!("{parent}");
quote! { Extension::#ident.bitmask() }
})
.collect::<Vec<_>>();
let ident = format_ident!("{id}");
Some(quote! { Extension::#ident => #(#requires)|*, })
})
.collect::<Vec<_>>();
let extension_names = isa
.extensions
.iter()
.map(|(id, ext)| {
let ident = format_ident!("{id}");
let name = &ext.name;
Some(quote! { Extension::#ident => #name, })
})
.collect::<Vec<_>>();
let extensions = quote! {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Extension {
#(#extension_variants)*
}
impl Extension {
#[inline]
pub const fn bitmask(self) -> u32 {
(1 << (self as u32)) | match self {
#(#extension_requires)*
_ => 0,
}
}
pub const fn name(self) -> &'static str {
match self {
#(#extension_names)*
}
}
}
};
let entries_count = Literal::usize_unsuffixed(entries.len());
let opcode_count = Literal::usize_unsuffixed(sorted_ops.len());
let max_args = Literal::usize_unsuffixed(max_args);
Ok(quote! {
#![allow(unused)]
#![cfg_attr(rustfmt, rustfmt_skip)]
#[comment = " Code generated by ppc750-genisa. DO NOT EDIT."]
#[comment = " Code generated by powerpc-genisa. DO NOT EDIT."]
use crate::disasm::*;
#extensions
#[doc = " The entry table allows us to quickly find the range of possible opcodes for a"]
#[doc = " given 6-bit prefix. 2*64 bytes should fit in a cache line (or two)."]
static OPCODE_ENTRIES: [(u8, u8); 64] = [#opcode_entries];
static OPCODE_ENTRIES: [(u16, u16); #entries_count] = [#opcode_entries];
#[derive(Copy, Clone)]
struct OpcodePattern {
bitmask: u32,
pattern: u32,
extensions: Extensions,
}
impl OpcodePattern {
const fn base(bitmask: u32, pattern: u32) -> Self {
Self { bitmask, pattern, extensions: Extensions::none() }
}
const fn extension(bitmask: u32, pattern: u32, extension: Extension) -> Self {
Self { bitmask, pattern, extensions: Extensions::from_extension(extension) }
}
}
#[doc = " The bitmask and pattern for each opcode."]
static OPCODE_PATTERNS: [(u32, u32); 256] = [#opcode_patterns];
static OPCODE_PATTERNS: [OpcodePattern; #opcode_count] = [#opcode_patterns];
#[doc = " The name of each opcode."]
static OPCODE_NAMES: [&str; 256] = [#opcode_names];
static OPCODE_NAMES: [&str; #opcode_count] = [#opcode_names];
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
#[repr(u8)]
#[repr(u16)]
#[non_exhaustive]
pub enum Opcode {
#[doc = " An illegal or unknown opcode"]
#[default]
Illegal = u8::MAX,
Illegal = u16::MAX,
#opcode_enum
}
impl Opcode {
#[inline]
pub fn _mnemonic(self) -> &'static str {
OPCODE_NAMES[self as usize]
pub fn mnemonic(self) -> &'static str {
OPCODE_NAMES.get(self as usize).copied().unwrap_or("<illegal>")
}
#[inline]
pub fn _detect(code: u32) -> Self {
pub fn detect(code: u32, extensions: Extensions) -> Self {
let entry = OPCODE_ENTRIES[(code >> 26) as usize];
for i in entry.0..entry.1 {
let pattern = OPCODE_PATTERNS[i as usize];
if (code & pattern.0) == pattern.1 {
#[comment = " Safety: The enum is repr(u8) and the value is within the enum's range"]
return unsafe { core::mem::transmute::<u8, Opcode>(i) };
let op = OPCODE_PATTERNS[i as usize];
if extensions.contains_all(op.extensions) && (code & op.bitmask) == op.pattern {
#[comment = " Safety: The enum is repr(u16) and the value is within the enum's range"]
return unsafe { core::mem::transmute::<u16, Opcode>(i) };
}
}
Self::Illegal
}
}
impl From<u8> for Opcode {
impl From<u16> for Opcode {
#[inline]
fn from(value: u8) -> Self {
fn from(value: u16) -> Self {
if value > #opcode_max {
Self::Illegal
} else {
#[comment = " Safety: The enum is repr(u8) and the value is within the enum's range"]
unsafe { core::mem::transmute::<u8, Self>(value) }
#[comment = " Safety: The enum is repr(u16) and the value is within the enum's range"]
unsafe { core::mem::transmute::<u16, Self>(value) }
}
}
}
impl From<Opcode> for u8 {
impl From<Opcode> for u16 {
#[inline]
fn from(value: Opcode) -> Self {
value as u8
value as u16
}
}
@ -373,28 +502,36 @@ pub fn gen_disasm(isa: &Isa, max_args: usize) -> Result<TokenStream> {
type MnemonicFunction = fn(&mut ParsedIns, Ins);
#mnemonic_functions
static BASIC_MNEMONICS: [MnemonicFunction; 256] = [#basic_functions_ref];
#[inline]
pub fn parse_basic(out: &mut ParsedIns, ins: Ins) {
BASIC_MNEMONICS[ins.op as usize](out, ins)
static BASIC_MNEMONICS: [MnemonicFunction; #opcode_count] = [#basic_functions_ref];
pub(crate) fn parse_basic(out: &mut ParsedIns, ins: Ins) {
match BASIC_MNEMONICS.get(ins.op as usize) {
Some(f) => f(out, ins),
None => mnemonic_illegal(out, ins),
}
}
static SIMPLIFIED_MNEMONICS: [MnemonicFunction; #opcode_count] = [#simplified_functions_ref];
pub(crate) fn parse_simplified(out: &mut ParsedIns, ins: Ins) {
match SIMPLIFIED_MNEMONICS.get(ins.op as usize) {
Some(f) => f(out, ins),
None => mnemonic_illegal(out, ins),
}
static SIMPLIFIED_MNEMONICS: [MnemonicFunction; 256] = [#simplified_functions_ref];
#[inline]
pub fn parse_simplified(out: &mut ParsedIns, ins: Ins) {
SIMPLIFIED_MNEMONICS[ins.op as usize](out, ins)
}
type DefsUsesFunction = fn(&mut Arguments, Ins);
#defs_uses_functions
static DEFS_FUNCTIONS: [DefsUsesFunction; 256] = [#defs_refs];
#[inline]
pub fn parse_defs(out: &mut Arguments, ins: Ins) {
DEFS_FUNCTIONS[ins.op as usize](out, ins)
static DEFS_FUNCTIONS: [DefsUsesFunction; #opcode_count] = [#defs_refs];
pub(crate) fn parse_defs(out: &mut Arguments, ins: Ins) {
match DEFS_FUNCTIONS.get(ins.op as usize) {
Some(f) => f(out, ins),
None => defs_uses_empty(out, ins),
}
}
static USES_FUNCTIONS: [DefsUsesFunction; #opcode_count] = [#uses_refs];
pub(crate) fn parse_uses(out: &mut Arguments, ins: Ins) {
match USES_FUNCTIONS.get(ins.op as usize) {
Some(f) => f(out, ins),
None => defs_uses_empty(out, ins),
}
static USES_FUNCTIONS: [DefsUsesFunction; 256] = [#uses_refs];
#[inline]
pub fn parse_uses(out: &mut Arguments, ins: Ins) {
USES_FUNCTIONS[ins.op as usize](out, ins)
}
})
}
@ -467,7 +604,7 @@ fn gen_mnemonic(
}
let names_len = Literal::usize_unsuffixed(names.len());
Ok(quote! { {
const MODIFIERS: [&str; #names_len] = [#(#names),*];
static MODIFIERS: [&str; #names_len] = [#(#names),*];
ParsedIns { mnemonic: MODIFIERS[#bitset], args: #arguments }
} })
}

View File

@ -1,17 +1,66 @@
use std::collections::HashMap;
use std::{fs::File, path::Path, str::FromStr};
use anyhow::{Context, Result};
use anyhow::{bail, Context, Result};
use indexmap::IndexMap;
use num_traits::PrimInt;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, ToTokens};
use serde::de::IntoDeserializer;
use serde::{Deserialize, Deserializer, Serialize};
pub fn load_isa(path: &Path) -> Result<Isa> {
let yaml_file =
File::open(path).with_context(|| format!("Failed to open file {}", path.display()))?;
let isa: Isa = serde_yaml::from_reader(yaml_file)
let mut isa: Isa = serde_yaml::from_reader(yaml_file)
.with_context(|| format!("While parsing file {}", path.display()))?;
// Merge in all extensions
for extension in isa.extensions.values() {
for field in &extension.fields {
if isa.find_field(&field.name).is_some() {
bail!(
"Field {} already exists (while applying extension {})",
field.name,
extension.name
);
} else {
isa.fields.push(field.clone());
}
}
for modifier in &extension.modifiers {
if isa.find_modifier(&modifier.name).is_some() {
bail!(
"Modifier {} already exists (while applying extension {})",
modifier.name,
extension.name
);
} else {
isa.modifiers.push(modifier.clone());
}
}
for opcode in &extension.opcodes {
if isa.find_opcode(&opcode.name).is_some() {
bail!(
"Opcode {} already exists (while applying extension {})",
opcode.name,
extension.name
);
} else {
isa.opcodes.push(opcode.clone());
}
}
for mnemonic in &extension.mnemonics {
if isa.find_mnemonic(&mnemonic.name).is_some() {
bail!(
"Mnemonic {} already exists (while applying extension {})",
mnemonic.name,
extension.name
);
} else {
isa.mnemonics.push(mnemonic.clone());
}
}
}
Ok(isa)
}
@ -22,6 +71,18 @@ pub struct Isa {
pub modifiers: Vec<Modifier>,
pub opcodes: Vec<Opcode>,
pub mnemonics: Vec<Mnemonic>,
pub extensions: IndexMap<String, Extension>,
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
#[serde(default)]
pub struct Extension {
pub name: String,
pub requires: Vec<String>,
pub fields: Vec<Field>,
pub modifiers: Vec<Modifier>,
pub opcodes: Vec<Opcode>,
pub mnemonics: Vec<Mnemonic>,
}
impl Isa {
@ -36,6 +97,10 @@ impl Isa {
pub fn find_opcode(&self, name: &str) -> Option<&Opcode> {
self.opcodes.iter().find(|o| o.name == name)
}
pub fn find_mnemonic(&self, name: &str) -> Option<&Mnemonic> {
self.mnemonics.iter().find(|m| m.name == name)
}
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
@ -44,9 +109,8 @@ pub struct Field {
pub name: String,
pub desc: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub bits: Option<BitRange>,
pub bits: Option<SplitBitRange>,
pub signed: bool,
pub split: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub arg: Option<String>,
pub shift_left: u8,
@ -55,16 +119,12 @@ pub struct Field {
impl Field {
/// Calculate the field mask from its bit range
pub fn mask(&self) -> u32 {
self.bits.map(|b| b.mask()).unwrap_or(0)
self.bits.as_ref().map(|b| b.mask()).unwrap_or(0)
}
/// Shift and mask a value according to the field
pub fn shift_value(&self, mut value: u32) -> u32 {
if self.split {
// Swap 5-bit halves (SPR, TBR)
value = ((value & 0b11111_00000u32) >> 5) | ((value & 0b00000_11111u32) << 5);
}
self.bits.map(|b| b.shift_value(value >> self.shift_left)).unwrap_or(0)
pub fn shift_value(&self, value: u32) -> u32 {
self.bits.as_ref().map(|b| b.shift_value(value >> self.shift_left)).unwrap_or(0)
}
pub fn ident(&self) -> Ident {
@ -226,6 +286,7 @@ pub fn modifiers_iter<'a>(
}
#[derive(Copy, Clone, Debug, Default)]
#[repr(transparent)]
pub struct BitRange(pub (u8, u8));
impl BitRange {
@ -235,42 +296,42 @@ impl BitRange {
}
#[inline]
pub fn start(&self) -> u8 {
pub fn start(self) -> u8 {
self.0 .0
}
#[inline]
pub fn end(&self) -> u8 {
pub fn end(self) -> u8 {
self.0 .1
}
/// Calculate the mask from the range
#[inline]
pub fn mask(&self) -> u32 {
pub fn mask(self) -> u32 {
self.max_value() << self.shift()
}
/// Number of bits to shift
#[inline]
pub fn shift(&self) -> u8 {
pub fn shift(self) -> u8 {
32 - self.end()
}
/// Number of bits in the range
#[inline]
pub fn len(&self) -> u8 {
pub fn len(self) -> u8 {
self.end() - self.start()
}
/// Shift and mask a value according to the range
#[inline]
pub fn shift_value(&self, value: u32) -> u32 {
pub fn shift_value(self, value: u32) -> u32 {
(value & self.max_value()) << self.shift()
}
/// Calculate the maximum value that can be represented by the range
#[inline]
pub fn max_value(&self) -> u32 {
pub fn max_value(self) -> u32 {
(1 << self.len()) - 1
}
}
@ -282,12 +343,12 @@ impl<'de> Deserialize<'de> for BitRange {
{
let range_str: String = Deserialize::deserialize(deserializer)?;
if let Some((start_str, end_str)) = range_str.split_once("..") {
let start = start_str.parse::<u8>().map_err(serde::de::Error::custom)?;
let end = end_str.parse::<u8>().map_err(serde::de::Error::custom)?;
let start = start_str.trim().parse::<u8>().map_err(serde::de::Error::custom)?;
let end = end_str.trim().parse::<u8>().map_err(serde::de::Error::custom)?;
Ok(Self::new(start, end))
} else {
let bit_idx = range_str.parse::<u8>().map_err(serde::de::Error::custom)?;
Ok(Self::new(bit_idx, bit_idx))
let bit_idx = range_str.trim().parse::<u8>().map_err(serde::de::Error::custom)?;
Ok(Self::new(bit_idx, bit_idx + 1))
}
}
}
@ -297,14 +358,100 @@ impl Serialize for BitRange {
where
S: serde::Serializer,
{
if self.start() == self.end() {
self.start().serialize(serializer)
if self.start() + 1 == self.end() {
self.start().to_string().serialize(serializer)
} else {
format!("{}..{}", self.start(), self.end()).serialize(serializer)
}
}
}
/// A collection of bit ranges, used to represent a (possibly non-contiguous) set of bits
#[derive(Clone, Debug, Default)]
pub struct SplitBitRange(pub Vec<BitRange>);
impl SplitBitRange {
#[inline]
pub fn end(&self) -> u8 {
self.iter().map(BitRange::end).max().unwrap_or(0)
}
/// Calculate the mask from the range
#[inline]
pub fn mask(&self) -> u32 {
self.iter().map(BitRange::mask).fold(0, |acc, m| acc | m)
}
/// Number of bits to shift
#[inline]
pub fn shift(&self) -> u8 {
32 - self.end()
}
/// Number of bits in the range
#[inline]
pub fn len(&self) -> u8 {
self.iter().map(BitRange::len).sum()
}
/// Shift and mask a value according to the range
#[inline]
pub fn shift_value(&self, mut value: u32) -> u32 {
let mut result = 0;
for range in self.iter().rev() {
result |= range.shift_value(value);
value >>= range.len();
}
result
}
/// Calculate the maximum value that can be represented by the range
#[inline]
pub fn max_value(&self) -> u32 {
(1 << self.len()) - 1
}
/// Create an iterator over the contained bit ranges
#[inline]
pub fn iter(&self) -> impl DoubleEndedIterator<Item = BitRange> + use<'_> {
self.0.iter().copied()
}
}
impl<'de> Deserialize<'de> for SplitBitRange {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let ranges_str: String = Deserialize::deserialize(deserializer)?;
let mut ranges = Vec::new();
for range_str in ranges_str.split(',') {
ranges.push(BitRange::deserialize(range_str.trim().into_deserializer())?);
}
Ok(Self(ranges))
}
}
impl Serialize for SplitBitRange {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut ranges_str = String::new();
for (i, range) in self.0.iter().enumerate() {
if i > 0 {
ranges_str.push(',');
}
if range.start() + 1 == range.end() {
ranges_str.push_str(&range.start().to_string());
} else {
ranges_str.push_str(&format!("{}..{}", range.start(), range.end()));
}
}
serializer.serialize_str(&ranges_str)
}
}
pub fn to_ident(key: &str) -> String {
key.to_ascii_lowercase().replace('.', "_").replace('+', "p").replace('-', "m")
}
@ -321,7 +468,7 @@ pub fn to_variant(key: &str) -> String {
s.push(match c {
'a'..='z' => c.to_ascii_uppercase(),
'A'..='Z' => c,
_ => panic!("invalid identifier: {}", key),
_ => panic!("invalid identifier: {key}"),
});
loop {
let c = match chars.next() {
@ -335,7 +482,7 @@ pub fn to_variant(key: &str) -> String {
s.push('_');
break;
}
_ => panic!("invalid character in variant: {}", key),
_ => panic!("invalid character in variant: {key}"),
}
}
}
@ -383,7 +530,7 @@ where
T: std::fmt::LowerHex,
{
fn to_tokens(&self, tokens: &mut TokenStream) {
let s = format!("{:#x}", self);
let s = format!("{self:#x}");
tokens.extend(TokenStream::from_str(&s).unwrap());
}
}
@ -410,7 +557,34 @@ where
T: PrimInt + std::fmt::LowerHex,
{
fn to_tokens(&self, tokens: &mut TokenStream) {
let s = format!("{:#x}", self);
let s = format!("{self:#x}");
tokens.extend(TokenStream::from_str(&s).unwrap());
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_split_bit_range() {
let range = SplitBitRange(vec![BitRange::new(26, 27), BitRange::new(21, 26)]);
assert_eq!(range.mask(), 0b0000_0000_0000_0000_0000_0111_1110_0000);
assert_eq!(range.shift(), 5);
assert_eq!(range.len(), 6);
assert_eq!(range.max_value(), 0x3F);
assert_eq!(range.shift_value(u32::MAX), 0b0000_0000_0000_0000_0000_0111_1110_0000);
assert_eq!(range.shift_value(0x1F), 0b0000_0000_0000_0000_0000_0111_1100_0000);
}
#[test]
fn test_split_bit_range_non_contiguous() {
let range = SplitBitRange(vec![BitRange::new(30, 31), BitRange::new(16, 21)]);
assert_eq!(range.mask(), 0b0000_0000_0000_0000_1111_1000_0000_0010);
assert_eq!(range.shift(), 1);
assert_eq!(range.len(), 6);
assert_eq!(range.max_value(), 0x3F);
assert_eq!(range.shift_value(u32::MAX), 0b0000_0000_0000_0000_1111_1000_0000_0010);
assert_eq!(range.shift_value(0x1F), 0b0000_0000_0000_0000_1111_1000_0000_0000);
}
}

View File

@ -5,7 +5,7 @@ mod isa;
use crate::asm::gen_asm;
use crate::disasm::gen_disasm;
use anyhow::{ensure, Context, Result};
use anyhow::{Context, Result};
use condition::{parse_conditions, ConditionOp, ConditionValue};
use isa::load_isa;
use std::path::Path;
@ -19,11 +19,10 @@ macro_rules! ident {
}
fn main() -> Result<()> {
simple_logger::SimpleLogger::new().env().init().unwrap();
simple_logger::SimpleLogger::new().env().init()?;
let isa = load_isa(Path::new("isa.yaml"))?;
// Make sure we can fit the opcodes into a u8
ensure!(isa.opcodes.len() <= 255);
log::info!("Opcode count: {}", isa.opcodes.len());
// Sanity check the opcodes and mnemonics
// Calculate the bitmask for each opcode and compare it to the stored bitmask

3292
isa.yaml

File diff suppressed because it is too large Load Diff