From a9cfac7ff14d93ff22092366f99eb2dd3213ea7e Mon Sep 17 00:00:00 2001 From: Rusty Wagner Date: Tue, 19 Dec 2023 22:39:33 -0700 Subject: Add RISC-V architecture plugin --- arch/riscv/CMakeLists.txt | 114 ++ arch/riscv/Cargo.lock | 368 +++++ arch/riscv/Cargo.toml | 22 + arch/riscv/README.md | 17 + arch/riscv/disasm/Cargo.toml | 8 + arch/riscv/disasm/src/lib.rs | 3141 +++++++++++++++++++++++++++++++++++++ arch/riscv/src/lib.rs | 2570 ++++++++++++++++++++++++++++++ arch/riscv/src/liftcheck.rs | 427 +++++ platform/linux/platform_linux.cpp | 48 + 9 files changed, 6715 insertions(+) create mode 100644 arch/riscv/CMakeLists.txt create mode 100644 arch/riscv/Cargo.lock create mode 100644 arch/riscv/Cargo.toml create mode 100644 arch/riscv/README.md create mode 100644 arch/riscv/disasm/Cargo.toml create mode 100644 arch/riscv/disasm/src/lib.rs create mode 100644 arch/riscv/src/lib.rs create mode 100644 arch/riscv/src/liftcheck.rs diff --git a/arch/riscv/CMakeLists.txt b/arch/riscv/CMakeLists.txt new file mode 100644 index 00000000..ffa28b7b --- /dev/null +++ b/arch/riscv/CMakeLists.txt @@ -0,0 +1,114 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(arch_riscv) + +file(GLOB PLUGIN_SOURCES CONFIGURE_DEPENDS + ../../binaryninjacore.h + ${PROJECT_SOURCE_DIR}/Cargo.toml + ${PROJECT_SOURCE_DIR}/src/*.rs + ${PROJECT_SOURCE_DIR}/disasm/src/*.rs) + +if(CMAKE_BUILD_TYPE MATCHES Debug) + set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/debug) + set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target) +else() + set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/release) + set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target --release) +endif() + +if(FORCE_COLORED_OUTPUT) + set(CARGO_OPTS ${CARGO_OPTS} --color always) +endif() + +set(CARGO_FEATURES "") +set(OUTPUT_FILE_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}arch_riscv${CMAKE_SHARED_LIBRARY_SUFFIX}) +set(OUTPUT_PDB_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}arch_riscv.pdb) +set(OUTPUT_FILE_PATH ${BN_CORE_PLUGIN_DIR}/${OUTPUT_FILE_NAME}) +set(OUTPUT_PDB_PATH ${BN_CORE_PLUGIN_DIR}/${OUTPUT_PDB_NAME}) + +set(BINJA_LIB_DIR ${BN_INSTALL_BIN_DIR}) + +add_custom_target(arch_riscv ALL DEPENDS ${OUTPUT_FILE_PATH}) +add_dependencies(arch_riscv binaryninjaapi) +get_target_property(BN_API_SOURCE_DIR binaryninjaapi SOURCE_DIR) +list(APPEND CMAKE_MODULE_PATH "${BN_API_SOURCE_DIR}/cmake") +find_package(BinaryNinjaCore REQUIRED) + +set_property(TARGET arch_riscv PROPERTY OUTPUT_FILE_PATH ${OUTPUT_FILE_PATH}) + +# Add the whole api to the depends too +file(GLOB_RECURSE API_SOURCES CONFIGURE_DEPENDS + ${BN_API_SOURCE_DIR}/rust/src/*.rs + ${BN_API_SOURCE_DIR}/rust/binaryninjacore-sys/src/*.rs) + +find_program(RUSTUP_PATH rustup REQUIRED HINTS ~/.cargo/bin) +set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_STABLE_VERSION} cargo) + +if(APPLE) + if(UNIVERSAL) + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/debug/${OUTPUT_FILE_NAME}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/debug/${OUTPUT_FILE_NAME}) + else() + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/release/${OUTPUT_FILE_NAME}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/release/${OUTPUT_FILE_NAME}) + endif() + + add_custom_command( + OUTPUT ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} clean --target=aarch64-apple-darwin ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} clean --target=x86_64-apple-darwin ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} build --target=aarch64-apple-darwin ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} build --target=x86_64-apple-darwin ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND lipo -create ${AARCH64_LIB_PATH} ${X86_64_LIB_PATH} -output ${OUTPUT_FILE_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES} + ) + else() + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(LIB_PATH ${PROJECT_BINARY_DIR}/target/debug/${OUTPUT_FILE_NAME}) + else() + set(LIB_PATH ${PROJECT_BINARY_DIR}/target/release/${OUTPUT_FILE_NAME}) + endif() + + add_custom_command( + OUTPUT ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} clean ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND ${CMAKE_COMMAND} -E copy ${LIB_PATH} ${OUTPUT_FILE_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES} + ) + endif() +elseif(WIN32) + add_custom_command( + OUTPUT ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} clean ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE_NAME} ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_PDB_NAME} ${OUTPUT_PDB_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES} + ) +else() + add_custom_command( + OUTPUT ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} clean ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE_NAME} ${OUTPUT_FILE_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES} + ) +endif() diff --git a/arch/riscv/Cargo.lock b/arch/riscv/Cargo.lock new file mode 100644 index 00000000..b98a9faa --- /dev/null +++ b/arch/riscv/Cargo.lock @@ -0,0 +1,368 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "arch_riscv" +version = "0.1.0" +dependencies = [ + "binaryninja", + "log", + "rayon", + "riscv-dis", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "binaryninja" +version = "0.1.0" +dependencies = [ + "binaryninjacore-sys", + "lazy_static", + "libc", + "log", + "rayon", +] + +[[package]] +name = "binaryninjacore-sys" +version = "0.1.0" +dependencies = [ + "bindgen", +] + +[[package]] +name = "bindgen" +version = "0.66.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "riscv-dis" +version = "0.1.0" +dependencies = [ + "byteorder", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "shlex" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" + +[[package]] +name = "syn" +version = "2.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "which" +version = "4.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" +dependencies = [ + "either", + "lazy_static", + "libc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/arch/riscv/Cargo.toml b/arch/riscv/Cargo.toml new file mode 100644 index 00000000..a9148e6e --- /dev/null +++ b/arch/riscv/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "arch_riscv" +version = "0.1.0" +authors = ["Ryan Snyder "] +edition = "2021" + +[dependencies] +binaryninja = { path = "../../rust" } +riscv-dis = { path = "disasm" } +log = "0.4" +rayon = { version = "1.0", optional = true } + +[features] +default = [] +liftcheck = ["rayon", "binaryninja/rayon"] + +[lib] +crate-type = ["cdylib"] + +[profile.release] +panic = "abort" +lto = true diff --git a/arch/riscv/README.md b/arch/riscv/README.md new file mode 100644 index 00000000..822f3c7a --- /dev/null +++ b/arch/riscv/README.md @@ -0,0 +1,17 @@ +# arch-riscv + +This is the RISC-V architecture plugin that ships with Binary Ninja. + +## Building + +Building the architecture plugin requires the Rust language development tools. + +Run `cargo build --release` to build the plugin. The plugin can be found in the `target/release` directory as `libarch_riscv.so`, `libarch_riscv.dylib` or `arch_riscv.dll` depending on your platform. + +To install the plugin, first launch Binary Ninja and uncheck the "RISC-V architecture plugin" option in the "Core Plugins" section. This will cause Binary Ninja to stop loading the bundled plugin so that its replacement can be loaded. Once this is complete, you can copy the plugin into the user plugins directory (you can locate this by using the "Open Plugin Folder" option in the Binary Ninja UI). + +**Do not replace the architecture plugin in the Binary Ninja install directory. This will be overwritten every time there is a Binary Ninja update. Use the above process to ensure that updates do not automatically uninstall your custom build.** + +## Pull Requests + +Please follow whatever formatting conventions are present in the file you edit. Pay attention to curly brackets, spacing, tabs vs. spaces, etc. diff --git a/arch/riscv/disasm/Cargo.toml b/arch/riscv/disasm/Cargo.toml new file mode 100644 index 00000000..a799dc18 --- /dev/null +++ b/arch/riscv/disasm/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "riscv-dis" +version = "0.1.0" +authors = ["Ryan Snyder "] +edition = "2021" + +[dependencies] +byteorder = "1" diff --git a/arch/riscv/disasm/src/lib.rs b/arch/riscv/disasm/src/lib.rs new file mode 100644 index 00000000..6762bb5d --- /dev/null +++ b/arch/riscv/disasm/src/lib.rs @@ -0,0 +1,3141 @@ +// TODO list +// op-imm shift amounts +// clean up the compressed stuff (esp MISC-ALU) +// finish transition to from_instr32 from 'new' +// make the various component structs smaller (8 bit IntReg/FloatReg etc.) + +extern crate byteorder; + +use std::borrow::Cow; +use std::fmt; +use std::marker::PhantomData; +use std::mem; + +use byteorder::{ByteOrder, LittleEndian}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Error { + TooShort, + UnhandledLength, + Unaligned, + InvalidOpcode, + InvalidSubop, + BadRegister, +} + +pub type DisResult = Result; + +#[derive(Copy, Clone, Debug)] +pub enum Op { + // + // RV32I + // + + // LOAD + Load(LoadTypeInst), + + // MISC-MEM + Fence(ITypeIntInst), + FenceI(ITypeIntInst), + + // OP-IMM + AddI(ITypeIntInst), + SltI(ITypeIntInst), + SltIU(ITypeIntInst), + XorI(ITypeIntInst), + OrI(ITypeIntInst), + AndI(ITypeIntInst), + SllI(ITypeIntInst), + SrlI(ITypeIntInst), + SraI(ITypeIntInst), + + // AUIPC + Auipc(UTypeInst), + + // STORE + Store(StoreTypeInst), + + // OP + Add(RTypeIntInst), + Sll(RTypeIntInst), + Slt(RTypeIntInst), + SltU(RTypeIntInst), + Xor(RTypeIntInst), + Srl(RTypeIntInst), + Or(RTypeIntInst), + And(RTypeIntInst), + Sub(RTypeIntInst), + Sra(RTypeIntInst), + + // LUI + Lui(UTypeInst), + + // BRANCH + Beq(BTypeInst), + Bne(BTypeInst), + Blt(BTypeInst), + Bge(BTypeInst), + BltU(BTypeInst), + BgeU(BTypeInst), + + // JALR + Jalr(ITypeIntInst), + + // JAL + Jal(JTypeInst), + + // SYSTEM + Ecall, + Ebreak, + Csrrw(ITypeIntInst), + Csrrs(ITypeIntInst), + Csrrc(ITypeIntInst), + CsrrwI(CsrITypeInst), + CsrrsI(CsrITypeInst), + CsrrcI(CsrITypeInst), + + Uret, + Sret, + Mret, + Wfi, + SfenceVma(RTypeIntInst), + + // + // RV64I + // + + // OP-IMM-32 + AddIW(ITypeIntInst), + SllIW(ITypeIntInst), + SrlIW(ITypeIntInst), + SraIW(ITypeIntInst), + + // OP-32 + AddW(RTypeIntInst), + SllW(RTypeIntInst), + SrlW(RTypeIntInst), + SubW(RTypeIntInst), + SraW(RTypeIntInst), + + // + // RV32M + // + + // OP + Mul(RTypeIntInst), + MulH(RTypeIntInst), + MulHSU(RTypeIntInst), + MulHU(RTypeIntInst), + Div(RTypeIntInst), + DivU(RTypeIntInst), + Rem(RTypeIntInst), + RemU(RTypeIntInst), + + // + // RV64M + // + + // OP-32 + MulW(RTypeIntInst), + DivW(RTypeIntInst), + DivUW(RTypeIntInst), + RemW(RTypeIntInst), + RemUW(RTypeIntInst), + + // + // RV32A + // + + // AMO + Lr(AtomicInst), + Sc(AtomicInst), + AmoSwap(AtomicInst), + AmoAdd(AtomicInst), + AmoXor(AtomicInst), + AmoAnd(AtomicInst), + AmoOr(AtomicInst), + AmoMin(AtomicInst), + AmoMax(AtomicInst), + AmoMinU(AtomicInst), + AmoMaxU(AtomicInst), + + // + // RV32F + // + LoadFp(FpMemInst), + StoreFp(FpMemInst), + Fmadd(FpMAddInst), + Fmsub(FpMAddInst), + Fnmsub(FpMAddInst), + Fnmadd(FpMAddInst), + Fadd(RTypeFloatRoundInst), + Fsub(RTypeFloatRoundInst), + Fmul(RTypeFloatRoundInst), + Fdiv(RTypeFloatRoundInst), + Fsqrt(RTypeFloatRoundInst), + Fsgnj(RTypeFloatInst), + Fsgnjn(RTypeFloatInst), + Fsgnjx(RTypeFloatInst), + Fmin(RTypeFloatInst), + Fmax(RTypeFloatInst), + Fle(RTypeFloatCmpInst), + Flt(RTypeFloatCmpInst), + Feq(RTypeFloatCmpInst), + Fcvt(FpCvtInst), + FcvtToInt(FpCvtToIntInst), + FcvtFromInt(FpCvtFromIntInst), + FmvToInt(FpMvToIntInst), + FmvFromInt(FpMvFromIntInst), + Fclass(FpClassInst), +} + +pub trait Register { + fn new(id: u32) -> Self; + + fn id(&self) -> u32; + fn valid(&self) -> bool; +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum RoundMode { + RoundNearestEven, + RoundTowardZero, + RoundDown, + RoundUp, + RoundMaxMagnitude, + Dynamic, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct IntReg { + reg_id: u8, + _dis: PhantomData, +} + +impl Register for IntReg { + #[inline(always)] + fn new(id: u32) -> Self { + let ret = Self { + reg_id: id as u8, + _dis: PhantomData, + }; + + debug_assert!(ret.valid()); + + ret + } + + #[inline(always)] + fn id(&self) -> u32 { + self.reg_id as u32 + } + + #[inline(always)] + fn valid(&self) -> bool { + (self.reg_id as u32) < ::int_reg_count() + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct FloatReg { + reg_id: u8, + _dis: PhantomData, +} + +impl Register for FloatReg { + #[inline(always)] + fn new(id: u32) -> Self { + let ret = Self { + reg_id: id as u8, + _dis: PhantomData, + }; + + debug_assert!(ret.valid()); + + ret + } + + #[inline(always)] + fn id(&self) -> u32 { + self.reg_id as u32 + } + + #[inline(always)] + fn valid(&self) -> bool { + (self.reg_id as u32) < ::int_reg_count() + } +} + +pub trait IntRegType: Sized { + #[inline(always)] + fn width() -> usize { + mem::size_of::() + } +} + +pub trait FloatRegType: Sized { + #[inline(always)] + fn width() -> usize { + mem::size_of::() + } + + #[inline(always)] + fn present() -> bool { + mem::size_of::() != 0 + } +} + +impl IntRegType for u32 {} +impl IntRegType for u64 {} +impl FloatRegType for () {} +impl FloatRegType for f32 {} +impl FloatRegType for f64 {} + +pub trait RegFile: Sized + Copy + Clone { + type Int: IntRegType; + type Float: FloatRegType; + + #[inline(always)] + fn int_reg_count() -> u32 { + 32 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct Rv32IRegs; +impl RegFile for Rv32IRegs { + type Int = u32; + type Float = (); +} + +#[derive(Copy, Clone, Debug)] +pub struct Rv32ERegs; +impl RegFile for Rv32ERegs { + type Int = u32; + type Float = (); + + #[inline(always)] + fn int_reg_count() -> u32 { + 16 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct Rv32GRegs; +impl RegFile for Rv32GRegs { + type Int = u32; + type Float = f64; +} + +#[derive(Copy, Clone, Debug)] +pub struct Rv64GRegs; +impl RegFile for Rv64GRegs { + type Int = u64; + type Float = f64; +} + +pub enum Operand { + R(IntReg), + F(FloatReg), + I(i32), + M(i32, IntReg), // reg + displacement + RM(RoundMode), +} + +impl fmt::Display for Operand { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &Operand::R(ref r) => write!(f, "x{}", r.id()), + &Operand::F(ref r) => write!(f, "f{}", r.id()), + &Operand::I(i) => match i { + -0x80000..=-1 => write!(f, "-{:x}", -i), + _ => write!(f, "{:x}", i), + }, + &Operand::M(i, ref r) => { + if i < 0 { + write!(f, "-{:x}(x{})", -i, r.id()) + } else { + write!(f, "{:x}(x{})", i, r.id()) + } + } + &Operand::RM(ref r) => write!(f, "{}", r.name()), + } + } +} + +#[derive(Copy, Clone, Debug)] +struct Instr32(u32); +impl Instr32 { + #[inline(always)] + fn extract_bits(self, start_bit: u32, width: u32) -> u32 { + self.0.wrapping_shr(start_bit) & 1u32.wrapping_shl(width).wrapping_sub(1) + } + + #[inline(always)] + fn opcode(self) -> u32 { + self.extract_bits(0, 7) + } + + #[inline(always)] + fn rd(self) -> u32 { + self.extract_bits(7, 5) + } + + #[inline(always)] + fn rs1(self) -> u32 { + self.extract_bits(15, 5) + } + + #[inline(always)] + fn rs2(self) -> u32 { + self.extract_bits(20, 5) + } + + #[inline(always)] + fn rs3(self) -> u32 { + self.extract_bits(27, 5) + } + + #[inline(always)] + fn funct3(self) -> u32 { + self.extract_bits(12, 3) + } + + #[inline(always)] + fn funct7(self) -> u32 { + self.extract_bits(25, 7) + } + + #[inline(always)] + fn rm(self) -> u32 { + self.extract_bits(12, 3) + } + + #[inline(always)] + fn fsize(self) -> u32 { + self.extract_bits(25, 2) + } + + #[inline(always)] + fn fop(self) -> u32 { + self.extract_bits(27, 5) + } + + #[inline(always)] + fn i_imm(self) -> i32 { + (self.0 as i32) >> 20 + } + + #[inline(always)] + fn s_imm(self) -> i32 { + (((self.0 as i32) >> 20) & !0x1f) | self.extract_bits(7, 5) as i32 + } + + #[inline(always)] + fn b_imm(self) -> i32 { + let b_imm = self.s_imm(); + (b_imm & !0x801) | ((b_imm & 1) << 11) + } + + #[inline(always)] + fn u_imm(self) -> i32 { + (self.0 & !0xfff) as i32 + } + + #[inline(always)] + fn j_imm(self) -> i32 { + let mut j_imm = (((self.0 as i32) >> 11) as u32) & 0xfff00000; + j_imm |= 0x000ff000 & self.0; + j_imm |= self.extract_bits(20, 11); + ((j_imm & !0x801) | ((j_imm & 1) << 11)) as i32 + } +} + +impl RoundMode { + fn from_bits(bits: u32) -> DisResult { + match bits { + 0b000 => Ok(RoundMode::RoundNearestEven), + 0b001 => Ok(RoundMode::RoundTowardZero), + 0b010 => Ok(RoundMode::RoundDown), + 0b011 => Ok(RoundMode::RoundUp), + 0b100 => Ok(RoundMode::RoundMaxMagnitude), + 0b111 => Ok(RoundMode::Dynamic), + _ => Err(Error::InvalidSubop), + } + } + + pub fn name(&self) -> &'static str { + match self { + RoundMode::RoundNearestEven => "rne", + RoundMode::RoundTowardZero => "rtz", + RoundMode::RoundDown => "rdn", + RoundMode::RoundUp => "rup", + RoundMode::RoundMaxMagnitude => "rmm", + RoundMode::Dynamic => "dyn", + } + } + + pub fn all() -> &'static [RoundMode] { + &[ + RoundMode::RoundNearestEven, + RoundMode::RoundTowardZero, + RoundMode::RoundDown, + RoundMode::RoundUp, + RoundMode::RoundMaxMagnitude, + RoundMode::Dynamic, + ] + } +} + +#[derive(Copy, Clone, Debug)] +pub struct LoadTypeInst { + width: u8, + zx: bool, + rd: IntReg, + rs1: IntReg, + imm: i16, + _dis: PhantomData, +} + +impl LoadTypeInst { + #[inline(always)] + fn new(width: usize, zx: bool, rd: IntReg, rs1: IntReg, imm: i32) -> DisResult { + if width + zx as usize > ::Int::width() { + return Err(Error::InvalidSubop); + } else if !rd.valid() || !rs1.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + zx, + rd, + rs1, + imm: imm as i16, + _dis: PhantomData, + }) + } + + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult { + let width = 1u32.wrapping_shl(inst.extract_bits(12, 2)) as u8; + let zx = inst.extract_bits(14, 1) == 1; + let rd = IntReg::new(inst.rd()); + let rs1 = IntReg::new(inst.rs1()); + + if width as usize + zx as usize > ::Int::width() { + return Err(Error::InvalidSubop); + } else if !rd.valid() || !rs1.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width, + zx, + rd, + rs1, + imm: inst.i_imm() as i16, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn width(&self) -> usize { + self.width as usize + } + + #[inline(always)] + pub fn zx(&self) -> bool { + self.zx + } + + #[inline(always)] + pub fn rd(&self) -> IntReg { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> IntReg { + self.rs1 + } + + #[inline(always)] + pub fn imm(&self) -> i32 { + self.imm as i32 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct StoreTypeInst { + width: u8, + rs1: IntReg, + rs2: IntReg, + imm: i16, + _dis: PhantomData, +} + +impl StoreTypeInst { + #[inline(always)] + fn new(width: usize, rs2: IntReg, rs1: IntReg, imm: i32) -> DisResult { + if width > ::Int::width() { + return Err(Error::InvalidSubop); + } else if !rs1.valid() || !rs2.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + rs1, + rs2, + imm: imm as i16, + _dis: PhantomData, + }) + } + + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult { + let width = 1u32.wrapping_shl(inst.extract_bits(12, 3)) as u8; + let rs1 = IntReg::new(inst.rs1()); + let rs2 = IntReg::new(inst.rs2()); + + if width as usize > ::Int::width() { + return Err(Error::InvalidSubop); + } else if !rs1.valid() || !rs2.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width, + rs1, + rs2, + imm: inst.s_imm() as i16, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn width(&self) -> usize { + self.width as usize + } + + #[inline(always)] + pub fn rs1(&self) -> IntReg { + self.rs1 + } + + #[inline(always)] + pub fn rs2(&self) -> IntReg { + self.rs2 + } + + #[inline(always)] + pub fn imm(&self) -> i32 { + self.imm as i32 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct ITypeInst +where + Rd: Register, + Rs1: Register, +{ + inst: Instr32, + _rd: PhantomData, + _rs1: PhantomData, +} + +pub type ITypeIntInst = ITypeInst, IntReg>; + +impl ITypeInst +where + Rd: Register, + Rs1: Register, +{ + #[inline(always)] + fn new(inst: Instr32) -> DisResult { + let ret = Self { + inst, + _rd: PhantomData, + _rs1: PhantomData, + }; + + if !ret.rd().valid() || !ret.rs1().valid() { + return Err(Error::BadRegister); + } + + Ok(ret) + } + + #[inline(always)] + fn from_ops(rd: Rd, rs1: Rs1, imm: i32) -> Self { + let imm = imm as u32; + let raw: u32 = (imm << 20) | (rd.id() << 7) | (rs1.id() << 15); + + Self { + inst: Instr32(raw), + _rd: PhantomData, + _rs1: PhantomData, + } + } + + #[inline(always)] + pub fn rd(&self) -> Rd { + Rd::new(self.inst.rd()) + } + + #[inline(always)] + pub fn rs1(&self) -> Rs1 { + Rs1::new(self.inst.rs1()) + } + + #[inline(always)] + pub fn imm(&self) -> i32 { + self.inst.i_imm() + } +} + +#[derive(Copy, Clone, Debug)] +pub struct CsrITypeInst { + inst: Instr32, + _dis: PhantomData, +} + +impl CsrITypeInst { + #[inline(always)] + fn new(inst: Instr32) -> DisResult { + let ret = Self { + inst, + _dis: PhantomData, + }; + + if !ret.rd().valid() { + return Err(Error::BadRegister); + } + + Ok(ret) + } + + #[inline(always)] + pub fn rd(&self) -> IntReg { + IntReg::new(self.inst.rd()) + } + + #[inline(always)] + pub fn imm(&self) -> u32 { + self.inst.rs1() + } + + #[inline(always)] + pub fn csr(&self) -> u32 { + self.inst.i_imm() as u32 & 0xfff + } +} + +#[derive(Copy, Clone, Debug)] +pub struct RTypeInst +where + Rd: Register, + Rs1: Register, + Rs2: Register, +{ + inst: Instr32, + _rd: PhantomData, + _rs1: PhantomData, + _rs2: PhantomData, +} + +pub type RTypeIntInst = RTypeInst, IntReg, IntReg>; + +impl RTypeInst +where + Rd: Register, + Rs1: Register, + Rs2: Register, +{ + #[inline(always)] + fn new(inst: Instr32) -> DisResult { + let ret = Self { + inst, + _rd: PhantomData, + _rs1: PhantomData, + _rs2: PhantomData, + }; + + if !ret.rd().valid() || !ret.rs1().valid() || !ret.rs2().valid() { + return Err(Error::BadRegister); + } + + Ok(ret) + } + + #[inline(always)] + fn from_ops(rd: Rd, rs1: Rs1, rs2: Rs2) -> Self { + let raw: u32 = (rd.id() << 7) | (rs1.id() << 15) | (rs2.id() << 20); + + Self { + inst: Instr32(raw), + _rd: PhantomData, + _rs1: PhantomData, + _rs2: PhantomData, + } + } + + #[inline(always)] + pub fn rd(&self) -> Rd { + Rd::new(self.inst.rd()) + } + + #[inline(always)] + pub fn rs1(&self) -> Rs1 { + Rs1::new(self.inst.rs1()) + } + + #[inline(always)] + pub fn rs2(&self) -> Rs2 { + Rs2::new(self.inst.rs2()) + } +} + +#[derive(Copy, Clone, Debug)] +pub struct RTypeFloatInst { + width: u8, + rd: FloatReg, + rs1: FloatReg, + rs2: FloatReg, +} + +impl RTypeFloatInst { + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult { + let width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(Error::InvalidSubop), + }; + + if width > ::Float::width() { + return Err(Error::InvalidSubop); + } + + let rd = FloatReg::new(inst.rd()); + let rs1 = FloatReg::new(inst.rs1()); + let rs2 = FloatReg::new(inst.rs2()); + + if !rd.valid() || !rs1.valid() || !rs2.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + rd, + rs1, + rs2, + }) + } + + #[inline(always)] + pub fn width(&self) -> u8 { + self.width + } + + #[inline(always)] + pub fn rd(&self) -> FloatReg { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> FloatReg { + self.rs1 + } + + #[inline(always)] + pub fn rs2(&self) -> FloatReg { + self.rs2 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct RTypeFloatCmpInst { + width: u8, + rd: IntReg, + rs1: FloatReg, + rs2: FloatReg, +} + +impl RTypeFloatCmpInst { + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult { + let width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(Error::InvalidSubop), + }; + + if width > ::Float::width() { + return Err(Error::InvalidSubop); + } + + let rd = IntReg::new(inst.rd()); + let rs1 = FloatReg::new(inst.rs1()); + let rs2 = FloatReg::new(inst.rs2()); + + if !rd.valid() || !rs1.valid() || !rs2.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + rd, + rs1, + rs2, + }) + } + + #[inline(always)] + pub fn width(&self) -> u8 { + self.width + } + + #[inline(always)] + pub fn rd(&self) -> IntReg { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> FloatReg { + self.rs1 + } + + #[inline(always)] + pub fn rs2(&self) -> FloatReg { + self.rs2 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct RTypeFloatRoundInst { + width: u8, + rd: FloatReg, + rs1: FloatReg, + rs2: FloatReg, + rm: RoundMode, +} + +impl RTypeFloatRoundInst { + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult { + let width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(Error::InvalidSubop), + }; + + if width > ::Float::width() { + return Err(Error::InvalidSubop); + } + + let rd = FloatReg::new(inst.rd()); + let rs1 = FloatReg::new(inst.rs1()); + let rs2 = FloatReg::new(inst.rs2()); + let rm = RoundMode::from_bits(inst.rm())?; + + if !rd.valid() || !rs1.valid() || !rs2.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + rd, + rs1, + rs2, + rm, + }) + } + + #[inline(always)] + pub fn width(&self) -> u8 { + self.width + } + + #[inline(always)] + pub fn rd(&self) -> FloatReg { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> FloatReg { + self.rs1 + } + + #[inline(always)] + pub fn rs2(&self) -> FloatReg { + self.rs2 + } + + #[inline(always)] + pub fn rm(&self) -> RoundMode { + self.rm + } +} + +#[derive(Copy, Clone, Debug)] +pub struct BTypeInst { + rs1: IntReg, + rs2: IntReg, + imm: i16, + _dis: PhantomData, +} + +impl BTypeInst { + #[inline(always)] + fn new(rs1: IntReg, rs2: IntReg, imm: i32) -> DisResult { + if !rs1.valid() || !rs2.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + rs1, + rs2, + imm: imm as i16, + _dis: PhantomData, + }) + } + + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult { + let rs1 = IntReg::new(inst.rs1()); + let rs2 = IntReg::new(inst.rs2()); + + if !rs1.valid() || !rs2.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + rs1, + rs2, + imm: inst.b_imm() as i16, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn rs1(&self) -> IntReg { + self.rs1 + } + + #[inline(always)] + pub fn rs2(&self) -> IntReg { + self.rs2 + } + + #[inline(always)] + pub fn imm(&self) -> i32 { + self.imm as i32 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct UTypeInst { + rd: IntReg, + imm: i32, + _dis: PhantomData, +} + +impl UTypeInst { + #[inline(always)] + fn new(rd: IntReg, imm: i32) -> DisResult { + if !rd.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + rd, + imm, + _dis: PhantomData, + }) + } + + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult { + let rd = IntReg::new(inst.rd()); + + if !rd.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + rd, + imm: inst.u_imm(), + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn rd(&self) -> IntReg { + self.rd + } + + #[inline(always)] + pub fn imm(&self) -> i32 { + self.imm + } +} + +#[derive(Copy, Clone, Debug)] +pub struct JTypeInst { + rd: IntReg, + imm: i32, + _dis: PhantomData, +} + +impl JTypeInst { + #[inline(always)] + fn new(rd: IntReg, imm: i32) -> DisResult { + if !rd.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + rd, + imm, + _dis: PhantomData, + }) + } + + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult { + let rd = IntReg::new(inst.rd()); + + if !rd.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + rd, + imm: inst.j_imm(), + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn rd(&self) -> IntReg { + self.rd + } + + #[inline(always)] + pub fn imm(&self) -> i32 { + self.imm + } +} + +#[derive(Copy, Clone, Debug)] +pub struct AtomicInst { + inst: Instr32, + _dis: PhantomData, +} + +impl AtomicInst { + #[inline(always)] + fn new(inst: Instr32) -> DisResult { + let ret = Self { + inst, + _dis: PhantomData, + }; + + let width = ret.width(); + + if width < 4 || width > ::Int::width() { + return Err(Error::InvalidSubop); + } else if !ret.rd().valid() || !ret.rs1().valid() || !ret.rs2().valid() { + return Err(Error::BadRegister); + } + + Ok(ret) + } + + #[inline(always)] + pub fn rd(&self) -> IntReg { + IntReg::new(self.inst.rd()) + } + + #[inline(always)] + pub fn rs1(&self) -> IntReg { + IntReg::new(self.inst.rs1()) + } + + #[inline(always)] + pub fn rs2(&self) -> IntReg { + IntReg::new(self.inst.rs2()) + } + + #[inline(always)] + pub fn width(&self) -> usize { + 1usize.wrapping_shl(self.inst.funct3()) + } + + #[inline(always)] + pub fn aq(&self) -> bool { + self.inst.extract_bits(26, 1) != 0 + } + + #[inline(always)] + pub fn rl(&self) -> bool { + self.inst.extract_bits(25, 1) != 0 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct FpMemInst { + width: u8, + fr: FloatReg, + rs1: IntReg, + imm: i16, + _dis: PhantomData, +} + +impl FpMemInst { + #[inline(always)] + fn new(width: usize, fr: FloatReg, rs1: IntReg, imm: i32) -> DisResult { + if width > ::Float::width() { + return Err(Error::InvalidSubop); + } else if !fr.valid() || !rs1.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + fr, + rs1, + imm: imm as i16, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn width(&self) -> usize { + self.width as usize + } + + #[inline(always)] + pub fn fr(&self) -> FloatReg { + self.fr + } + + #[inline(always)] + pub fn rs1(&self) -> IntReg { + self.rs1 + } + + #[inline(always)] + pub fn imm(&self) -> i32 { + self.imm as i32 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct FpMAddInst { + width: u8, + rd: FloatReg, + rs1: FloatReg, + rs2: FloatReg, + rs3: FloatReg, + rm: RoundMode, + _dis: PhantomData, +} + +impl FpMAddInst { + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult { + let width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(Error::InvalidSubop), + }; + + if width > ::Float::width() { + return Err(Error::InvalidSubop); + } + + let rd = FloatReg::new(inst.rd()); + let rs1 = FloatReg::new(inst.rs1()); + let rs2 = FloatReg::new(inst.rs2()); + let rs3 = FloatReg::new(inst.rs3()); + let rm = RoundMode::from_bits(inst.rm())?; + + if !rd.valid() || !rs1.valid() || !rs2.valid() || !rs3.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + rd, + rs1, + rs2, + rs3, + rm, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn width(&self) -> u8 { + self.width + } + + #[inline(always)] + pub fn rd(&self) -> FloatReg { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> FloatReg { + self.rs1 + } + + #[inline(always)] + pub fn rs2(&self) -> FloatReg { + self.rs2 + } + + #[inline(always)] + pub fn rs3(&self) -> FloatReg { + self.rs3 + } + + #[inline(always)] + pub fn rm(&self) -> RoundMode { + self.rm + } +} + +#[derive(Copy, Clone, Debug)] +pub struct FpCvtInst { + rd_width: u8, + rs1_width: u8, + rd: FloatReg, + rs1: FloatReg, + rm: RoundMode, + _dis: PhantomData, +} + +impl FpCvtInst { + #[inline(always)] + fn new( + rd: FloatReg, + rd_width: u8, + rs1: FloatReg, + rs1_width: u8, + rm: RoundMode, + ) -> DisResult { + Ok(Self { + rd_width, + rs1_width, + rd, + rs1, + rm, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn rd_width(&self) -> u8 { + self.rd_width + } + + #[inline(always)] + pub fn rs1_width(&self) -> u8 { + self.rs1_width + } + + #[inline(always)] + pub fn rd(&self) -> FloatReg { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> FloatReg { + self.rs1 + } + + #[inline(always)] + pub fn rm(&self) -> RoundMode { + self.rm + } +} + +#[derive(Copy, Clone, Debug)] +pub struct FpCvtToIntInst { + rd_width: u8, + rs1_width: u8, + zx: bool, + rd: IntReg, + rs1: FloatReg, + rm: RoundMode, + _dis: PhantomData, +} + +impl FpCvtToIntInst { + #[inline(always)] + fn new( + rd: IntReg, + rd_width: u8, + zx: bool, + rs1: FloatReg, + rs1_width: u8, + rm: RoundMode, + ) -> DisResult { + Ok(Self { + rd_width, + rs1_width, + zx, + rd, + rs1, + rm, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn rd_width(&self) -> u8 { + self.rd_width + } + + #[inline(always)] + pub fn rs1_width(&self) -> u8 { + self.rs1_width + } + + #[inline(always)] + pub fn zx(&self) -> bool { + self.zx + } + + #[inline(always)] + pub fn rd(&self) -> IntReg { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> FloatReg { + self.rs1 + } + + #[inline(always)] + pub fn rm(&self) -> RoundMode { + self.rm + } +} + +#[derive(Copy, Clone, Debug)] +pub struct FpCvtFromIntInst { + rd_width: u8, + rs1_width: u8, + zx: bool, + rd: FloatReg, + rs1: IntReg, + rm: RoundMode, + _dis: PhantomData, +} + +impl FpCvtFromIntInst { + #[inline(always)] + fn new( + rd: FloatReg, + rd_width: u8, + rs1: IntReg, + rs1_width: u8, + zx: bool, + rm: RoundMode, + ) -> DisResult { + Ok(Self { + rd_width, + rs1_width, + zx, + rd, + rs1, + rm, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn rd_width(&self) -> u8 { + self.rd_width + } + + #[inline(always)] + pub fn rs1_width(&self) -> u8 { + self.rs1_width + } + + #[inline(always)] + pub fn zx(&self) -> bool { + self.zx + } + + #[inline(always)] + pub fn rd(&self) -> FloatReg { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> IntReg { + self.rs1 + } + + #[inline(always)] + pub fn rm(&self) -> RoundMode { + self.rm + } +} + +#[derive(Copy, Clone, Debug)] +pub struct FpMvToIntInst { + width: u8, + rd: IntReg, + rs1: FloatReg, + _dis: PhantomData, +} + +impl FpMvToIntInst { + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult { + let width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(Error::InvalidSubop), + }; + + if width > ::Float::width() + || width > ::Int::width() + { + return Err(Error::InvalidSubop); + } + + let rd = IntReg::new(inst.rd()); + let rs1 = FloatReg::new(inst.rs1()); + + if !rd.valid() || !rs1.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + rd, + rs1, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn width(&self) -> u8 { + self.width + } + + #[inline(always)] + pub fn rd(&self) -> IntReg { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> FloatReg { + self.rs1 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct FpMvFromIntInst { + width: u8, + rd: FloatReg, + rs1: IntReg, + _dis: PhantomData, +} + +impl FpMvFromIntInst { + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult { + let width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(Error::InvalidSubop), + }; + + if width > ::Float::width() + || width > ::Int::width() + { + return Err(Error::InvalidSubop); + } + + let rd = FloatReg::new(inst.rd()); + let rs1 = IntReg::new(inst.rs1()); + + if !rd.valid() || !rs1.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + rd, + rs1, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn width(&self) -> u8 { + self.width + } + + #[inline(always)] + pub fn rd(&self) -> FloatReg { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> IntReg { + self.rs1 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct FpClassInst { + width: u8, + rd: IntReg, + rs1: FloatReg, + _dis: PhantomData, +} + +impl FpClassInst { + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult { + let width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(Error::InvalidSubop), + }; + + if width > ::Float::width() { + return Err(Error::InvalidSubop); + } + + let rd = IntReg::new(inst.rd()); + let rs1 = FloatReg::new(inst.rs1()); + + if !rd.valid() || !rs1.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + rd, + rs1, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn width(&self) -> u8 { + self.width + } + + #[inline(always)] + pub fn rd(&self) -> IntReg { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> FloatReg { + self.rs1 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct Instr16(u16); +impl Instr16 { + #[inline(always)] + fn extract_bits(self, start_bit: u32, width: u32) -> u16 { + self.0.wrapping_shr(start_bit) & 1u16.wrapping_shl(width).wrapping_sub(1) + } + + #[inline(always)] + fn sp_load_imm(self, size: usize) -> i32 { + let size = size as u32 >> 3; + let start = 4 + size; + + let res = (self.extract_bits(start, 3 - size) << (2 + size)) + | (self.extract_bits(2, 2 + size) << 6); + + (res | (self.extract_bits(12, 1) << 5)) as i32 + } + + #[inline(always)] + fn mem_imm(self, size: usize) -> i32 { + let upper = self.extract_bits(10, 3) << 3; + + let res = match size { + 4 => upper | self.extract_bits(6, 1) << 2 | self.extract_bits(5, 1) << 6, + 8 => upper | self.extract_bits(5, 2) << 6, + _ => unimplemented!(), + }; + + res as i32 + } + + #[inline(always)] + fn sp_store_imm(self, size: usize) -> i32 { + let size = size as u32 >> 3; + let start = 9 + size; + + ((self.extract_bits(start, 4 - size) << (2 + size)) | (self.extract_bits(7, 2 + size) << 6)) + as i32 + } + + #[inline(always)] + fn cb_imm(self) -> i32 { + let mut imm = self.extract_bits(2, 1) << 5; + imm |= self.extract_bits(3, 2) << 1; + imm |= self.extract_bits(5, 2) << 6; + imm |= self.extract_bits(10, 2) << 3; + + if self.extract_bits(12, 1) != 0 { + imm |= !0xff; + } + + imm as i16 as i32 + } + + #[inline(always)] + fn cj_imm(self) -> i32 { + let mut imm = self.extract_bits(2, 1) << 5; + imm |= self.extract_bits(3, 3) << 1; + imm |= self.extract_bits(6, 1) << 7; + imm |= self.extract_bits(7, 1) << 6; + imm |= self.extract_bits(8, 1) << 10; + imm |= self.extract_bits(9, 2) << 8; + imm |= self.extract_bits(11, 1) << 4; + + if self.extract_bits(12, 1) != 0 { + imm |= !0x7ff; + } + + imm as i16 as i32 + } +} + +pub enum Instr { + Rv16(Op), + Rv32(Op), +} + +impl Instr { + pub fn mnem(&self) -> Mnem { + Mnem(&self) + } + + pub fn operands(&self) -> Vec> { + let mut ops = Vec::new(); + + match *self { + //Instr::Rv16(..) => {} + Instr::Rv32(ref op) | Instr::Rv16(ref op) => match *op { + Op::Load(ref l) => { + ops.push(Operand::R(l.rd())); + ops.push(Operand::M(l.imm(), l.rs1())); + } + Op::Store(ref s) => { + ops.push(Operand::R(s.rs2())); + ops.push(Operand::M(s.imm(), s.rs1())); + } + Op::Fence(ref i) + | Op::FenceI(ref i) + | Op::AddI(ref i) + | Op::SltI(ref i) + | Op::SltIU(ref i) + | Op::XorI(ref i) + | Op::OrI(ref i) + | Op::AndI(ref i) + | Op::SllI(ref i) + | Op::SrlI(ref i) + | Op::SraI(ref i) + | Op::AddIW(ref i) + | Op::SllIW(ref i) + | Op::SrlIW(ref i) + | Op::SraIW(ref i) => { + ops.push(Operand::R(i.rd())); + ops.push(Operand::R(i.rs1())); + ops.push(Operand::I(i.imm())); + } + Op::Auipc(ref u) | Op::Lui(ref u) => { + ops.push(Operand::R(u.rd())); + ops.push(Operand::I(u.imm())); + } + Op::Add(ref r) + | Op::Sll(ref r) + | Op::Slt(ref r) + | Op::SltU(ref r) + | Op::Xor(ref r) + | Op::Srl(ref r) + | Op::Or(ref r) + | Op::And(ref r) + | Op::Sub(ref r) + | Op::Sra(ref r) + | Op::AddW(ref r) + | Op::SllW(ref r) + | Op::SrlW(ref r) + | Op::SubW(ref r) + | Op::SraW(ref r) + | Op::Mul(ref r) + | Op::MulH(ref r) + | Op::MulHSU(ref r) + | Op::MulHU(ref r) + | Op::Div(ref r) + | Op::DivU(ref r) + | Op::Rem(ref r) + | Op::RemU(ref r) + | Op::MulW(ref r) + | Op::DivW(ref r) + | Op::DivUW(ref r) + | Op::RemW(ref r) + | Op::RemUW(ref r) => { + ops.push(Operand::R(r.rd())); + ops.push(Operand::R(r.rs1())); + ops.push(Operand::R(r.rs2())); + } + Op::Beq(ref b) + | Op::Bne(ref b) + | Op::Blt(ref b) + | Op::Bge(ref b) + | Op::BltU(ref b) + | Op::BgeU(ref b) => { + ops.push(Operand::R(b.rs1())); + ops.push(Operand::R(b.rs2())); + ops.push(Operand::I(b.imm())); + } + Op::Jalr(ref i) => { + ops.push(Operand::R(i.rd())); + ops.push(Operand::R(i.rs1())); + ops.push(Operand::I(i.imm())); + } + Op::Jal(ref j) => { + ops.push(Operand::R(j.rd())); + ops.push(Operand::I(j.imm())); + } + Op::SfenceVma(ref r) => { + ops.push(Operand::R(r.rs1())); + ops.push(Operand::R(r.rs2())); + } + Op::Csrrw(ref i) | Op::Csrrs(ref i) | Op::Csrrc(ref i) => { + ops.push(Operand::R(i.rd())); + ops.push(Operand::I(i.imm())); + ops.push(Operand::R(i.rs1())); + } + Op::CsrrwI(ref i) | Op::CsrrsI(ref i) | Op::CsrrcI(ref i) => { + ops.push(Operand::R(i.rd())); + ops.push(Operand::I(i.csr() as i32)); + ops.push(Operand::I(i.imm() as i32)); + } + Op::Ecall | Op::Ebreak | Op::Uret | Op::Sret | Op::Mret | Op::Wfi => {} + Op::Lr(ref a) + | Op::Sc(ref a) + | Op::AmoSwap(ref a) + | Op::AmoAdd(ref a) + | Op::AmoXor(ref a) + | Op::AmoAnd(ref a) + | Op::AmoOr(ref a) + | Op::AmoMin(ref a) + | Op::AmoMax(ref a) + | Op::AmoMinU(ref a) + | Op::AmoMaxU(ref a) => { + ops.push(Operand::R(a.rd())); + + if let Op::Lr(..) = *op { + } else { + ops.push(Operand::R(a.rs2())); + } + + ops.push(Operand::M(0, a.rs1())); + } + Op::LoadFp(ref m) | Op::StoreFp(ref m) => { + ops.push(Operand::F(m.fr())); + ops.push(Operand::M(m.imm(), m.rs1())); + } + Op::Fmadd(ref f) | Op::Fmsub(ref f) | Op::Fnmadd(ref f) | Op::Fnmsub(ref f) => { + ops.push(Operand::F(f.rd())); + ops.push(Operand::F(f.rs1())); + ops.push(Operand::F(f.rs2())); + ops.push(Operand::F(f.rs3())); + if f.rm() != RoundMode::Dynamic { + ops.push(Operand::RM(f.rm())); + } + } + Op::Fadd(ref f) | Op::Fsub(ref f) | Op::Fmul(ref f) | Op::Fdiv(ref f) => { + ops.push(Operand::F(f.rd())); + ops.push(Operand::F(f.rs1())); + ops.push(Operand::F(f.rs2())); + if f.rm() != RoundMode::Dynamic { + ops.push(Operand::RM(f.rm())); + } + } + Op::Fsqrt(ref f) => { + ops.push(Operand::F(f.rd())); + ops.push(Operand::F(f.rs1())); + if f.rm() != RoundMode::Dynamic { + ops.push(Operand::RM(f.rm())); + } + } + Op::Fsgnj(ref f) + | Op::Fsgnjn(ref f) + | Op::Fsgnjx(ref f) + | Op::Fmin(ref f) + | Op::Fmax(ref f) => { + ops.push(Operand::F(f.rd())); + ops.push(Operand::F(f.rs1())); + ops.push(Operand::F(f.rs2())); + } + Op::Fle(ref f) | Op::Flt(ref f) | Op::Feq(ref f) => { + ops.push(Operand::R(f.rd())); + ops.push(Operand::F(f.rs1())); + ops.push(Operand::F(f.rs2())); + } + Op::Fcvt(ref f) => { + ops.push(Operand::F(f.rd())); + ops.push(Operand::F(f.rs1())); + if f.rm() != RoundMode::Dynamic { + ops.push(Operand::RM(f.rm())); + } + } + Op::FcvtToInt(ref f) => { + ops.push(Operand::R(f.rd())); + ops.push(Operand::F(f.rs1())); + if f.rm() != RoundMode::Dynamic { + ops.push(Operand::RM(f.rm())); + } + } + Op::FcvtFromInt(ref f) => { + ops.push(Operand::F(f.rd())); + ops.push(Operand::R(f.rs1())); + if f.rm() != RoundMode::Dynamic { + ops.push(Operand::RM(f.rm())); + } + } + Op::FmvToInt(ref f) => { + ops.push(Operand::R(f.rd())); + ops.push(Operand::F(f.rs1())); + } + Op::FmvFromInt(ref f) => { + ops.push(Operand::F(f.rd())); + ops.push(Operand::R(f.rs1())); + } + Op::Fclass(ref f) => { + ops.push(Operand::R(f.rd())); + ops.push(Operand::F(f.rs1())); + } + }, + } + + ops + } +} + +pub struct Mnem<'a, D: RiscVDisassembler + 'a>(&'a Instr); +impl<'a, D: RiscVDisassembler + 'a> Mnem<'a, D> { + fn mnem(&self) -> &str { + match self.0 { + &Instr::Rv32(ref op) | &Instr::Rv16(ref op) => match *op { + Op::Load(..) => "l", + Op::Fence(..) => "fence", + Op::FenceI(..) => "fence.i", + + Op::AddI(..) => "addi", + Op::SltI(..) => "slti", + Op::SltIU(..) => "sltiu", + Op::XorI(..) => "xori", + Op::OrI(..) => "ori", + Op::AndI(..) => "andi", + Op::SllI(..) => "slli", + Op::SrlI(..) => "srli", + Op::SraI(..) => "srai", + + Op::Auipc(..) => "auipc", + + Op::AddIW(..) => "addiw", + Op::SllIW(..) => "slliw", + Op::SrlIW(..) => "srliw", + Op::SraIW(..) => "sraiw", + + Op::Store(..) => "s", + + Op::Lr(..) => "lr", + Op::Sc(..) => "sc", + Op::AmoSwap(..) => "amoswap", + Op::AmoAdd(..) => "amoadd", + Op::AmoXor(..) => "amoxor", + Op::AmoAnd(..) => "amoand", + Op::AmoOr(..) => "amoor", + Op::AmoMin(..) => "amoamin", + Op::AmoMax(..) => "amoamax", + Op::AmoMinU(..) => "amoaminu", + Op::AmoMaxU(..) => "amoamaxu", + + Op::Add(..) => "add", + Op::Sll(..) => "sll", + Op::Slt(..) => "slt", + Op::SltU(..) => "sltu", + Op::Xor(..) => "xor", + Op::Srl(..) => "srl", + Op::Or(..) => "or", + Op::And(..) => "and", + Op::Sub(..) => "sub", + Op::Sra(..) => "sra", + + Op::Mul(..) => "mul", + Op::MulH(..) => "mulh", + Op::MulHSU(..) => "mulhsu", + Op::MulHU(..) => "mulhu", + Op::Div(..) => "div", + Op::DivU(..) => "divu", + Op::Rem(..) => "rem", + Op::RemU(..) => "remu", + + Op::Lui(..) => "lui", + + Op::AddW(..) => "addw", + Op::SllW(..) => "sllw", + Op::SrlW(..) => "srlw", + Op::SubW(..) => "subw", + Op::SraW(..) => "sraw", + + Op::MulW(..) => "mulw", + Op::DivW(..) => "divw", + Op::DivUW(..) => "divuw", + Op::RemW(..) => "remw", + Op::RemUW(..) => "remuw", + + Op::Beq(..) => "beq", + Op::Bne(..) => "bne", + Op::Blt(..) => "blt", + Op::Bge(..) => "bge", + Op::BltU(..) => "bltu", + Op::BgeU(..) => "bgeu", + + Op::Jalr(..) => "jalr", + Op::Jal(..) => "jal", + + Op::Ecall => "ecall", + Op::Ebreak => "ebreak", + + Op::Uret => "uret", + Op::Sret => "sret", + Op::Mret => "mret", + + Op::Wfi => "wfi", + + Op::SfenceVma(..) => "sfence.vma", + + Op::Csrrw(..) => "csrrw", + Op::Csrrs(..) => "csrrs", + Op::Csrrc(..) => "csrrc", + + Op::CsrrwI(..) => "csrrwi", + Op::CsrrsI(..) => "csrrsi", + Op::CsrrcI(..) => "csrrci", + + Op::LoadFp(..) => "fl", + Op::StoreFp(..) => "fs", + + Op::Fmadd(..) => "fmadd", + Op::Fmsub(..) => "fmsub", + Op::Fnmadd(..) => "fnmadd", + Op::Fnmsub(..) => "fnmsub", + Op::Fadd(..) => "fadd", + Op::Fsub(..) => "fsub", + Op::Fmul(..) => "fmul", + Op::Fdiv(..) => "fdiv", + Op::Fsqrt(..) => "fsqrt", + Op::Fsgnj(..) => "fsgnj", + Op::Fsgnjn(..) => "fsgnjn", + Op::Fsgnjx(..) => "fsgnjx", + Op::Fmin(..) => "fmin", + Op::Fmax(..) => "fmax", + Op::Fle(..) => "fle", + Op::Flt(..) => "flt", + Op::Feq(..) => "feq", + Op::Fcvt(..) | Op::FcvtToInt(..) | Op::FcvtFromInt(..) => "fcvt", + Op::FmvToInt(..) | Op::FmvFromInt(..) => "fmv", + Op::Fclass(..) => "fclass", + }, + } + } + + fn suffix(&self) -> Option> { + match self.0 { + // &Instr::Rv16(_) => None, + &Instr::Rv32(ref op) | &Instr::Rv16(ref op) => match *op { + Op::Load(ref l) => { + let zx = if l.zx() { "u" } else { "" }; + let width = match l.width() { + 1 => "b", + 2 => "h", + 4 => "w", + 8 => "d", + _ => unreachable!(), + }; + + Some(format!("{}{}", width, zx).into()) + } + Op::Store(ref s) => Some( + match s.width() { + 1 => "b", + 2 => "h", + 4 => "w", + 8 => "d", + _ => unreachable!(), + } + .into(), + ), + Op::Lr(ref a) + | Op::Sc(ref a) + | Op::AmoSwap(ref a) + | Op::AmoAdd(ref a) + | Op::AmoXor(ref a) + | Op::AmoAnd(ref a) + | Op::AmoOr(ref a) + | Op::AmoMin(ref a) + | Op::AmoMax(ref a) + | Op::AmoMinU(ref a) + | Op::AmoMaxU(ref a) => { + let width = match a.width() { + 4 => ".w", + 8 => ".d", + _ => unreachable!(), + }; + let aq = if a.aq() { ".aq" } else { "" }; + let rl = if a.rl() { ".rl" } else { "" }; + + Some(format!("{}{}{}", width, aq, rl).into()) + } + Op::LoadFp(ref m) | Op::StoreFp(ref m) => { + let suf = match m.width() { + 4 => "w", + 8 => "d", + 16 => "q", + _ => unreachable!(), + }; + + Some(suf.into()) + } + Op::Fmadd(ref f) | Op::Fmsub(ref f) | Op::Fnmadd(ref f) | Op::Fnmsub(ref f) => { + let suf = match f.width() { + 4 => ".s", + 8 => ".d", + 16 => ".q", + _ => unreachable!(), + }; + + Some(suf.into()) + } + Op::Fadd(ref f) | Op::Fsub(ref f) | Op::Fmul(ref f) | Op::Fdiv(ref f) => { + let suf = match f.width() { + 4 => ".s", + 8 => ".d", + 16 => ".q", + _ => unreachable!(), + }; + + Some(suf.into()) + } + Op::Fsgnj(ref f) + | Op::Fsgnjn(ref f) + | Op::Fsgnjx(ref f) + | Op::Fmin(ref f) + | Op::Fmax(ref f) => { + let suf = match f.width() { + 4 => ".s", + 8 => ".d", + 16 => ".q", + _ => unreachable!(), + }; + + Some(suf.into()) + } + Op::Fle(ref f) | Op::Flt(ref f) | Op::Feq(ref f) => { + let suf = match f.width() { + 4 => ".s", + 8 => ".d", + 16 => ".q", + _ => unreachable!(), + }; + + Some(suf.into()) + } + Op::Fcvt(ref f) => { + let rd_suf = match f.rd_width() { + 4 => ".s", + 8 => ".d", + 16 => ".q", + _ => unreachable!(), + }; + let rs1_suf = match f.rs1_width() { + 4 => ".s", + 8 => ".d", + 16 => ".q", + _ => unreachable!(), + }; + + Some(format!("{}{}", rd_suf, rs1_suf).into()) + } + Op::FcvtToInt(ref f) => { + let rd_suf = match f.rd_width() { + 4 => ".w", + 8 => ".l", + _ => unreachable!(), + }; + let zx_suf = if f.zx() { "u" } else { "" }; + let rs1_suf = match f.rs1_width() { + 4 => ".s", + 8 => ".d", + 16 => ".q", + _ => unreachable!(), + }; + + Some(format!("{}{}{}", rd_suf, zx_suf, rs1_suf).into()) + } + Op::FcvtFromInt(ref f) => { + let rd_suf = match f.rs1_width() { + 4 => ".s", + 8 => ".d", + 16 => ".q", + _ => unreachable!(), + }; + let rs1_suf = match f.rd_width() { + 4 => ".w", + 8 => ".l", + _ => unreachable!(), + }; + let zx_suf = if f.zx() { "u" } else { "" }; + + Some(format!("{}{}{}", rd_suf, rs1_suf, zx_suf).into()) + } + Op::FmvToInt(ref f) => { + let suf = match f.width() { + 4 => ".x.w", + 8 => ".x.d", + _ => unreachable!(), + }; + + Some(suf.into()) + } + Op::FmvFromInt(ref f) => { + let suf = match f.width() { + 4 => ".w.x", + 8 => ".d.x", + _ => unreachable!(), + }; + + Some(suf.into()) + } + Op::Fclass(ref f) => { + let suf = match f.width() { + 4 => ".s", + 8 => ".d", + 16 => ".q", + _ => unreachable!(), + }; + + Some(suf.into()) + } + _ => None, + }, + } + } +} + +impl<'a, D: RiscVDisassembler> fmt::Display for Mnem<'a, D> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match (self.mnem(), self.suffix()) { + (m, None) => f.pad(m), + (m, Some(s)) => { + let s = format!("{}{}", m, s); + f.pad(&s) + } + } + } +} + +pub trait StandardExtension { + fn supported() -> bool; +} + +pub struct ExtensionNotImplemented; +impl StandardExtension for ExtensionNotImplemented { + #[inline(always)] + fn supported() -> bool { + false + } +} + +pub struct ExtensionSupported; +impl StandardExtension for ExtensionSupported { + #[inline(always)] + fn supported() -> bool { + true + } +} + +pub trait RiscVDisassembler: Sized + Copy + Clone { + type RegFile: RegFile; + type MulDivExtension: StandardExtension; + type AtomicExtension: StandardExtension; + type CompressedExtension: StandardExtension; + + fn decode(addr: u64, bytes: &[u8]) -> DisResult> { + use Error::*; + + let required_alignment: u64 = if Self::CompressedExtension::supported() { + 2 + } else { + 4 + }; + + if addr & required_alignment.wrapping_sub(1) != 0 + || bytes.len() < required_alignment as usize + { + return Err(Unaligned); + } + + let parcel = LittleEndian::read_u16(bytes); + + let inst_len = match parcel { + p if (p & 0b11) != 0b11 => 2, + p if (p & 0b11111) != 0b11111 => 4, + _ => { + return Err(UnhandledLength); + } + }; + + match inst_len { + 2 if bytes.len() >= 2 && Self::CompressedExtension::supported() => { + let inst = Instr16(parcel); + + // top 3 bits and bottom 2 bits make up + // the bulk of the opcode map + // see: Table 12.3 RVC Opcode Map RISCV spec 2.2 + let opcode = (parcel >> 11 & !3) | (parcel & 3); + let int_width = ::Int::width(); + let float_width = ::Float::width(); + + let decoded = match opcode { + 0b000_00 if parcel != 0 => { + // ADDI4SPN + let rd = 8 + inst.extract_bits(2, 3) as u32; + let mut imm = inst.extract_bits(5, 1) << 3; + imm |= inst.extract_bits(6, 1) << 2; + imm |= inst.extract_bits(7, 4) << 6; + imm |= inst.extract_bits(11, 2) << 4; + + if imm == 0 { + return Err(InvalidSubop); + } + + Op::AddI(ITypeInst::from_ops( + IntReg::new(rd), + IntReg::new(2), + imm as i32, + )) + } + 0b000_01 => { + // ADDI + let rd = inst.extract_bits(7, 5) as u32; + let mut imm = inst.extract_bits(2, 5) as u32; + + // sign extend the 6 bit immediate value + if inst.extract_bits(12, 1) == 1 { + imm |= !0x1f; + } + + Op::AddI(ITypeInst::from_ops( + IntReg::new(rd), + IntReg::new(rd), + imm as i32, + )) + } + // shift amounts >= 32 are prohibited for Rv32 (reserved for NSE) + 0b000_10 if int_width >= 8 || inst.extract_bits(12, 1) == 0 => { + // SLLI + // TODO merge shamt extraction + let shamt = + (inst.extract_bits(12, 1) << 5 | inst.extract_bits(2, 5)) as u32; + let rd = inst.extract_bits(7, 5) as u32; + + // TODO Rv128 shamt of 0 == 64 + + Op::SllI(ITypeInst::from_ops( + IntReg::new(rd), + IntReg::new(rd), + shamt as i32, + )) + } + + //0b001_00 if int_width == 16 => unimplemented!("LQ"), + 0b001_00 if float_width >= 8 => { + // FLD + let rd = FloatReg::new(8 + inst.extract_bits(2, 3) as u32); + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + + let imm = inst.mem_imm(8); + Op::LoadFp(FpMemInst::new(8, rd, rs1, imm)?) + } + 0b001_01 if int_width == 4 => { + // JAL + Op::Jal(JTypeInst::new(IntReg::new(0), inst.cj_imm())?) + } + 0b001_01 => { + // ADDIW + let rd = inst.extract_bits(7, 5) as u32; + let mut imm = inst.extract_bits(2, 5) as u32; + + // TODO rd == zero valid? + // sign extend the 6 bit immediate value + if inst.extract_bits(12, 1) == 1 { + imm |= !0x1f; + } + + Op::AddIW(ITypeInst::from_ops( + IntReg::new(rd), + IntReg::new(rd), + imm as i32, + )) + } + //0b001_10 if int_width == 16 => unimplemented!("LQSP"), + 0b001_10 if float_width >= 8 => { + // FLDSP + let rd = FloatReg::new(inst.extract_bits(7, 5) as u32); + let imm = inst.sp_load_imm(8); + + Op::LoadFp(FpMemInst::new(8, rd, IntReg::new(2), imm)?) + } + + 0b010_00 => { + // LW + let rd = IntReg::new(8 + inst.extract_bits(2, 3) as u32); + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + + let imm = inst.mem_imm(4); + Op::Load(LoadTypeInst::new(4, false, rd, rs1, imm)?) + } + 0b010_01 => { + // LI + let rd = inst.extract_bits(7, 5) as u32; + + // TODO rd == 0 behavior + if rd == 0 { + return Err(InvalidSubop); + } + + // sign extend the 6 bit immediate value + let mut imm = inst.extract_bits(2, 5) as u32; + if inst.extract_bits(12, 1) == 1 { + imm |= !0x1f; + } + + Op::AddI(ITypeInst::from_ops( + IntReg::new(rd), + IntReg::new(0), + imm as i32, + )) + } + 0b010_10 => { + // LWSP + let rd = IntReg::new(inst.extract_bits(7, 5) as u32); + let imm = inst.sp_load_imm(4); + + if rd.id() == 0 { + return Err(InvalidOpcode); + } + + Op::Load(LoadTypeInst::new(4, false, rd, IntReg::new(2), imm)?) + } + + 0b011_00 if int_width >= 8 => { + // LD + let rd = IntReg::new(8 + inst.extract_bits(2, 3) as u32); + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + + let imm = inst.mem_imm(8); + Op::Load(LoadTypeInst::new(8, false, rd, rs1, imm)?) + } + 0b011_00 if float_width >= 4 => { + // FLW + let rd = FloatReg::new(8 + inst.extract_bits(2, 3) as u32); + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + + let imm = inst.mem_imm(4); + Op::LoadFp(FpMemInst::new(4, rd, rs1, imm)?) + } + 0b011_01 => { + // LUI/ADDI16SP + let rd = inst.extract_bits(7, 5) as u32; + + match rd { + 0 => return Err(InvalidSubop), + 2 => { + // ADDI16SP + let mut imm = inst.extract_bits(2, 1) << 5; + imm |= inst.extract_bits(3, 2) << 7; + imm |= inst.extract_bits(5, 1) << 6; + imm |= inst.extract_bits(6, 1) << 4; + + if inst.extract_bits(12, 1) == 1 { + imm |= !0x1ff; + } + + if imm == 0 { + return Err(InvalidSubop); + } + + Op::AddI(ITypeInst::from_ops( + IntReg::new(2), + IntReg::new(2), + imm as i16 as i32, + )) + } + _ => { + // sign extend the 6 bit immediate value + let mut imm = inst.extract_bits(2, 5) as u32; + if inst.extract_bits(12, 1) == 1 { + imm |= !0x1f; + } + + let imm = (imm as i32) << 12; + Op::Lui(UTypeInst::new(IntReg::new(rd), imm)?) + } + } + } + 0b011_10 if int_width >= 8 => { + // LDSP + let rd = IntReg::new(inst.extract_bits(7, 5) as u32); + let imm = inst.sp_load_imm(8); + + if rd.id() == 0 { + return Err(InvalidOpcode); + } + + Op::Load(LoadTypeInst::new(8, false, rd, IntReg::new(2), imm)?) + } + 0b011_10 if float_width >= 4 => { + // FLWSP + let rd = FloatReg::new(inst.extract_bits(7, 5) as u32); + let imm = inst.sp_load_imm(4); + + Op::LoadFp(FpMemInst::new(4, rd, IntReg::new(2), imm)?) + } + + // 0b100_00 RESERVED + 0b100_01 => { + // MISC-ALU + let rd = 8 + inst.extract_bits(7, 3) as u32; + + // TODO merge shamt extraction + let shamt = + (inst.extract_bits(12, 1) << 5 | inst.extract_bits(2, 5)) as u32; + + let mut mask = inst.extract_bits(2, 5) as u32; + if inst.extract_bits(12, 1) == 1 { + mask |= !0x1f; + } + + // TODO is shamt 0 actually prohibited? + // TODO should not always check this... + //if shamt == 0 || (int_width == 4 && shamt >= 32) { + // return Err(InvalidSubop); + //} + + match inst.extract_bits(10, 2) { + 0 => Op::SrlI(ITypeInst::from_ops( + IntReg::new(rd), + IntReg::new(rd), + shamt as i32, + )), // SRLI + 1 => Op::SraI(ITypeInst::from_ops( + IntReg::new(rd), + IntReg::new(rd), + shamt as i32, + )), // SRAI + 2 => Op::AndI(ITypeInst::from_ops( + IntReg::new(rd), + IntReg::new(rd), + mask as i32, + )), // ANDI + 3 => { + let op = inst.extract_bits(5, 2) | (inst.extract_bits(12, 1) << 2); + let rs2 = 8 + inst.extract_bits(2, 3) as u32; + let rtype = RTypeInst::from_ops( + IntReg::new(rd), + IntReg::new(rd), + IntReg::new(rs2), + ); + + match op { + 0 => Op::Sub(rtype), // SUB + 1 => Op::Xor(rtype), // XOR + 2 => Op::Or(rtype), // OR + 3 => Op::And(rtype), // AND + 4 if int_width >= 8 => Op::SubW(rtype), // SUBW + 5 if int_width >= 8 => Op::AddW(rtype), // ADDW + _ => return Err(InvalidSubop), + } + } + _ => unreachable!(), + } + } + 0b100_10 => { + // JALR/MV/ADD + let reg1 = inst.extract_bits(7, 5) as u32; + let reg2 = inst.extract_bits(2, 5) as u32; + let bit = inst.extract_bits(12, 1) as u32; + + match (bit, reg1, reg2) { + (link, rs1, 0) if rs1 != 0 => { + // JR, JALR + Op::Jalr(ITypeInst::from_ops( + IntReg::new(link), + IntReg::new(rs1), + 0, + )) + } + (0, rd, rs2) if rd != 0 && rs2 != 0 => { + // MV + Op::AddI(ITypeInst::from_ops(IntReg::new(rd), IntReg::new(rs2), 0)) + } + (1, 0, 0) => Op::Ebreak, + (1, rd, rs2) if rs2 != 0 => { + // ADD + Op::Add(RTypeInst::from_ops( + IntReg::new(rd), + IntReg::new(rd), + IntReg::new(rs2), + )) + } + _ => return Err(InvalidSubop), + } + } + + //0b101_00 if int_width == 16 => unimplemented!("SQ"), + 0b101_00 if float_width >= 8 => { + // FSD + let rd = FloatReg::new(8 + inst.extract_bits(2, 3) as u32); + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + + let imm = inst.mem_imm(8); + Op::StoreFp(FpMemInst::new(8, rd, rs1, imm)?) + } + 0b101_01 => { + // J + Op::Jal(JTypeInst::new(IntReg::new(0), inst.cj_imm())?) + } + //0b101_10 if int_width == 16 => unimplemented!("SQSP"), + 0b101_10 if float_width >= 8 => { + // FSDSP + let rd = FloatReg::new(inst.extract_bits(7, 5) as u32); + let imm = inst.sp_load_imm(8); + + Op::StoreFp(FpMemInst::new(8, rd, IntReg::new(2), imm)?) + } + + 0b110_00 => { + // SW + let rs2 = IntReg::new(8 + inst.extract_bits(2, 3) as u32); + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + + let imm = inst.mem_imm(4); + Op::Store(StoreTypeInst::new(4, rs2, rs1, imm)?) + } + 0b110_01 => { + // BEQZ + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + Op::Beq(BTypeInst::new(rs1, IntReg::new(0), inst.cb_imm())?) + } + 0b110_10 => { + // SWSP + let rs2 = IntReg::new(inst.extract_bits(2, 5) as u32); + let imm = inst.sp_store_imm(4); + + Op::Store(StoreTypeInst::new(4, rs2, IntReg::new(2), imm)?) + } + + 0b111_00 if int_width >= 8 => { + // SD + let rs2 = IntReg::new(8 + inst.extract_bits(2, 3) as u32); + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + + let imm = inst.mem_imm(8); + Op::Store(StoreTypeInst::new(8, rs2, rs1, imm)?) + } + 0b111_00 if float_width >= 4 => { + // FSW + let rd = FloatReg::new(8 + inst.extract_bits(2, 3) as u32); + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + + let imm = inst.mem_imm(4); + Op::StoreFp(FpMemInst::new(4, rd, rs1, imm)?) + } + 0b111_01 => { + // BNEZ + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + Op::Bne(BTypeInst::new(rs1, IntReg::new(0), inst.cb_imm())?) + } + 0b111_10 if int_width >= 8 => { + // SDSP + let rs2 = IntReg::new(inst.extract_bits(2, 5) as u32); + let imm = inst.sp_store_imm(8); + + Op::Store(StoreTypeInst::new(8, rs2, IntReg::new(2), imm)?) + } + 0b111_10 if float_width >= 4 => { + // FSWSP + let rd = FloatReg::new(inst.extract_bits(7, 5) as u32); + let imm = inst.sp_load_imm(4); + + Op::StoreFp(FpMemInst::new(4, rd, IntReg::new(2), imm)?) + } + + _ => return Err(InvalidOpcode), + }; + + Ok(Instr::Rv16(decoded)) + } + 4 if bytes.len() >= 4 => { + let inst = LittleEndian::read_u32(bytes); + let inst = Instr32(inst); + + let int_width = ::Int::width(); + let float_width = ::Float::width(); + + let decoded = match inst.opcode() >> 2 { + 0b00000 => Op::Load(LoadTypeInst::from_instr32(inst)?), // LOAD + 0b00001 if float_width > 0 => { + // LOAD-FP + let width = 1usize.wrapping_shl(inst.funct3()); + let fr = FloatReg::new(inst.rd()); + let rs1 = IntReg::new(inst.rs1()); + let imm = inst.i_imm(); + + if width < 4 || width > float_width { + return Err(InvalidSubop); + } + + Op::LoadFp(FpMemInst::new(width, fr, rs1, imm)?) + } + // TODO CUSTOM_0 + 0b00011 => { + // MISC-MEM + let itype = ITypeInst::new(inst)?; + + match inst.funct3() { + 0b000 => Op::Fence(itype), + 0b001 => Op::FenceI(itype), + _ => return Err(InvalidSubop), + } + } + 0b00100 => { + // OP-IMM + let mut itype = ITypeInst::new(inst)?; + match inst.funct3() { + 0b000 => Op::AddI(itype), + 0b010 => Op::SltI(itype), + 0b011 => Op::SltIU(itype), + 0b100 => Op::XorI(itype), + 0b110 => Op::OrI(itype), + 0b111 => Op::AndI(itype), + 0b001 => Op::SllI(itype), // TODO shamt + 0b101 => { + if inst.0 & 0x40000000 == 0 { + Op::SrlI(itype) + } else { + // pretty terrible hack, whatever + itype.inst.0 &= !0x40000000; + Op::SraI(itype) + } + } + _ => unreachable!(), + } + } + + 0b00101 => Op::Auipc(UTypeInst::from_instr32(inst)?), // AUIPC + + 0b00110 if int_width > 4 => { + // OP-IMM-32 + let mut itype = ITypeInst::new(inst)?; + match inst.funct3() { + 0b000 => Op::AddIW(itype), + 0b001 => Op::SllIW(itype), // TODO shamt + 0b101 => { + if inst.0 & 0x40000000 == 0 { + Op::SrlIW(itype) + } else { + // pretty terrible hack, whatever + itype.inst.0 &= !0x40000000; + Op::SraIW(itype) + } + } + _ => return Err(InvalidSubop), + } + } + + 0b01000 => Op::Store(StoreTypeInst::from_instr32(inst)?), // STORE + 0b01001 if float_width > 0 => { + // STORE-FP + let width = 1usize.wrapping_shl(inst.funct3()); + let fr = FloatReg::new(inst.rs2()); + let rs1 = IntReg::new(inst.rs1()); + let imm = inst.s_imm(); + + if width < 4 || width > float_width { + return Err(InvalidSubop); + } + + Op::StoreFp(FpMemInst::new(width, fr, rs1, imm)?) + } + // TODO CUSTOM_1 + 0b01011 if Self::AtomicExtension::supported() => { + // AMO + let atomic = AtomicInst::new(inst)?; + + // lower two bits represent aq/rl + match inst.funct7() >> 2 { + 0b00010 if inst.rs2() == 0 => Op::Lr(atomic), + 0b00011 => Op::Sc(atomic), + 0b00001 => Op::AmoSwap(atomic), + 0b00000 => Op::AmoAdd(atomic), + 0b00100 => Op::AmoXor(atomic), + 0b01100 => Op::AmoAnd(atomic), + 0b01000 => Op::AmoOr(atomic), + 0b10000 => Op::AmoMin(atomic), + 0b10100 => Op::AmoMax(atomic), + 0b11000 => Op::AmoMinU(atomic), + 0b11100 => Op::AmoMaxU(atomic), + _ => return Err(InvalidSubop), + } + } + 0b01100 => { + // OP + let rtype = RTypeIntInst::new(inst)?; + match inst.funct7() { + 0b0000000 => match inst.funct3() { + 0b000 => Op::Add(rtype), + 0b001 => Op::Sll(rtype), + 0b010 => Op::Slt(rtype), + 0b011 => Op::SltU(rtype), + 0b100 => Op::Xor(rtype), + 0b101 => Op::Srl(rtype), + 0b110 => Op::Or(rtype), + 0b111 => Op::And(rtype), + _ => unreachable!(), + }, + 0b0100000 => match inst.funct3() { + 0b000 => Op::Sub(rtype), + 0b101 => Op::Sra(rtype), + _ => return Err(InvalidSubop), + }, + 0b0000001 if Self::MulDivExtension::supported() => { + match inst.funct3() { + 0b000 => Op::Mul(rtype), + 0b001 => Op::MulH(rtype), + 0b010 => Op::MulHSU(rtype), + 0b011 => Op::MulHU(rtype), + 0b100 => Op::Div(rtype), + 0b101 => Op::DivU(rtype), + 0b110 => Op::Rem(rtype), + 0b111 => Op::RemU(rtype), + _ => unreachable!(), + } + } + _ => return Err(InvalidSubop), + } + } + 0b01101 => Op::Lui(UTypeInst::from_instr32(inst)?), // LUI + 0b01110 if int_width > 4 => { + // OP-32 + let rtype = RTypeIntInst::new(inst)?; + match inst.funct7() { + 0b0000000 => match inst.funct3() { + 0b000 => Op::AddW(rtype), + 0b001 => Op::SllW(rtype), + 0b101 => Op::SrlW(rtype), + _ => return Err(InvalidSubop), + }, + 0b0100000 => match inst.funct3() { + 0b000 => Op::SubW(rtype), + 0b101 => Op::SraW(rtype), + _ => return Err(InvalidSubop), + }, + 0b0000001 if Self::MulDivExtension::supported() => { + match inst.funct3() { + 0b000 => Op::MulW(rtype), + 0b100 => Op::DivW(rtype), + 0b101 => Op::DivUW(rtype), + 0b110 => Op::RemW(rtype), + 0b111 => Op::RemUW(rtype), + _ => return Err(InvalidSubop), + } + } + _ => return Err(InvalidSubop), + } + } + 0b10000 if float_width > 0 => Op::Fmadd(FpMAddInst::from_instr32(inst)?), // MADD + 0b10001 if float_width > 0 => Op::Fmsub(FpMAddInst::from_instr32(inst)?), // MSUB + 0b10010 if float_width > 0 => Op::Fnmsub(FpMAddInst::from_instr32(inst)?), // NMSUB + 0b10011 if float_width > 0 => Op::Fnmadd(FpMAddInst::from_instr32(inst)?), // NMADD + 0b10100 if float_width > 0 => { + // OP-FP + match inst.fop() { + 0b00000 => Op::Fadd(RTypeFloatRoundInst::from_instr32(inst)?), + 0b00001 => Op::Fsub(RTypeFloatRoundInst::from_instr32(inst)?), + 0b00010 => Op::Fmul(RTypeFloatRoundInst::from_instr32(inst)?), + 0b00011 => Op::Fdiv(RTypeFloatRoundInst::from_instr32(inst)?), + 0b00100 => match inst.funct3() { + 0b000 => Op::Fsgnj(RTypeFloatInst::from_instr32(inst)?), + 0b001 => Op::Fsgnjn(RTypeFloatInst::from_instr32(inst)?), + 0b010 => Op::Fsgnjx(RTypeFloatInst::from_instr32(inst)?), + _ => return Err(InvalidSubop), + }, + 0b00101 => match inst.funct3() { + 0b000 => Op::Fmin(RTypeFloatInst::from_instr32(inst)?), + 0b001 => Op::Fmax(RTypeFloatInst::from_instr32(inst)?), + _ => return Err(InvalidSubop), + }, + 0b01000 => { + let rd_width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(InvalidSubop), + }; + let rs1_width = match inst.rs2() { + 0b00000 => 4, + 0b00001 => 8, + 0b00011 => 16, + _ => return Err(InvalidSubop), + }; + + if rd_width > float_width + || rs1_width > float_width + || rd_width == rs1_width + { + return Err(InvalidSubop); + } + + let rd = FloatReg::new(inst.rd()); + let rs1 = FloatReg::new(inst.rs1()); + let rm = RoundMode::from_bits(inst.rm())?; + + Op::Fcvt(FpCvtInst::new( + rd, + rd_width as u8, + rs1, + rs1_width as u8, + rm, + )?) + } + 0b10100 => match inst.funct3() { + 0b000 => Op::Fle(RTypeFloatCmpInst::from_instr32(inst)?), + 0b001 => Op::Flt(RTypeFloatCmpInst::from_instr32(inst)?), + 0b010 => Op::Feq(RTypeFloatCmpInst::from_instr32(inst)?), + _ => return Err(InvalidSubop), + }, + 0b01011 if inst.rs2() == 0 => { + Op::Fsqrt(RTypeFloatRoundInst::from_instr32(inst)?) + } + 0b11000 => { + let rs1_width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(InvalidSubop), + }; + + if rs1_width > float_width { + return Err(InvalidSubop); + } + + let rd = IntReg::new(inst.rd()); + let rs1 = FloatReg::new(inst.rs1()); + let rm = RoundMode::from_bits(inst.rm())?; + + match inst.rs2() { + 0b00000 => Op::FcvtToInt(FpCvtToIntInst::new( + rd, + 4, + false, + rs1, + rs1_width as u8, + rm, + )?), + 0b00001 => Op::FcvtToInt(FpCvtToIntInst::new( + rd, + 4, + true, + rs1, + rs1_width as u8, + rm, + )?), + 0b00010 if int_width >= 8 => { + Op::FcvtToInt(FpCvtToIntInst::new( + rd, + 8, + false, + rs1, + rs1_width as u8, + rm, + )?) + } + 0b00011 if int_width >= 8 => Op::FcvtToInt( + FpCvtToIntInst::new(rd, 8, true, rs1, rs1_width as u8, rm)?, + ), + _ => return Err(InvalidSubop), + } + } + 0b11010 => { + let rd_width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(InvalidSubop), + }; + + if rd_width > float_width { + return Err(InvalidSubop); + } + + let rd = FloatReg::new(inst.rd()); + let rs1 = IntReg::new(inst.rs1()); + let rm = RoundMode::from_bits(inst.rm())?; + + match inst.rs2() { + 0b00000 => Op::FcvtFromInt(FpCvtFromIntInst::new( + rd, + rd_width as u8, + rs1, + 4, + false, + rm, + )?), + 0b00001 => Op::FcvtFromInt(FpCvtFromIntInst::new( + rd, + rd_width as u8, + rs1, + 4, + true, + rm, + )?), + 0b00010 if int_width >= 8 => { + Op::FcvtFromInt(FpCvtFromIntInst::new( + rd, + rd_width as u8, + rs1, + 8, + false, + rm, + )?) + } + 0b00011 if int_width >= 8 => { + Op::FcvtFromInt(FpCvtFromIntInst::new( + rd, + rd_width as u8, + rs1, + 8, + true, + rm, + )?) + } + _ => return Err(InvalidSubop), + } + } + 0b11100 if inst.rs2() == 0 => match inst.funct3() { + 0b000 => Op::FmvToInt(FpMvToIntInst::from_instr32(inst)?), + 0b001 => Op::Fclass(FpClassInst::from_instr32(inst)?), + _ => return Err(InvalidSubop), + }, + 0b11110 if inst.rs2() == 0 && inst.funct3() == 0 => { + Op::FmvFromInt(FpMvFromIntInst::from_instr32(inst)?) + } + _ => return Err(InvalidSubop), + } + } + // TODO CUSTOM_2 + 0b11000 => { + // BRANCH + let btype = BTypeInst::from_instr32(inst)?; + match inst.funct3() { + 0b000 => Op::Beq(btype), + 0b001 => Op::Bne(btype), + 0b100 => Op::Blt(btype), + 0b101 => Op::Bge(btype), + 0b110 => Op::BltU(btype), + 0b111 => Op::BgeU(btype), + _ => return Err(InvalidSubop), + } + } + 0b11001 if inst.funct3() == 0b000 => Op::Jalr(ITypeIntInst::new(inst)?), // JALR + 0b11011 => Op::Jal(JTypeInst::from_instr32(inst)?), // JAL + 0b11100 => { + // SYSTEM + match inst.funct3() { + 0b000 => { + let funct12 = inst.0 >> 20; + + match funct12 { + // Uses the low 5 bits to hold a register, + // everything else treats this as an immediate + // or ignores it + f if (f & 0xfe0) == 0x120 => { + Op::SfenceVma(RTypeIntInst::new(inst)?) + } + + 0x000 => Op::Ecall, + 0x001 => Op::Ebreak, + + 0x002 => Op::Uret, + 0x102 => Op::Sret, + 0x302 => Op::Mret, + + 0x105 => Op::Wfi, + _ => return Err(InvalidSubop), + } + } + 0b001 => Op::Csrrw(ITypeIntInst::new(inst)?), + 0b010 => Op::Csrrs(ITypeIntInst::new(inst)?), + 0b011 => Op::Csrrc(ITypeIntInst::new(inst)?), + 0b101 => Op::CsrrwI(CsrITypeInst::new(inst)?), + 0b110 => Op::CsrrsI(CsrITypeInst::new(inst)?), + 0b111 => Op::CsrrcI(CsrITypeInst::new(inst)?), + _ => return Err(InvalidSubop), + } + } + + _ => return Err(InvalidOpcode), + }; + + Ok(Instr::Rv32(decoded)) + } + _ => return Err(TooShort), + } + } +} + +#[derive(Copy, Clone, Debug)] +pub struct RiscVIMACDisassembler(PhantomData); +impl RiscVDisassembler for RiscVIMACDisassembler { + type RegFile = RF; + type MulDivExtension = ExtensionSupported; + type AtomicExtension = ExtensionSupported; + type CompressedExtension = ExtensionSupported; +} diff --git a/arch/riscv/src/lib.rs b/arch/riscv/src/lib.rs new file mode 100644 index 00000000..20136114 --- /dev/null +++ b/arch/riscv/src/lib.rs @@ -0,0 +1,2570 @@ +// Option -> Result +// rework operands/instruction text +// helper func for reading/writing to registers +// do the amo max/min instructions +// Platform api + +use std::borrow::Cow; +use std::fmt; +use std::marker::PhantomData; + +use binaryninja::{ + add_optional_plugin_dependency, architecture, + architecture::{ + llvm_assemble, Architecture, ArchitectureExt, CoreArchitecture, CustomArchitectureHandle, + ImplicitRegisterExtend, InstructionInfo, LlvmServicesCodeModel, LlvmServicesDialect, + LlvmServicesRelocMode, Register as Reg, RegisterInfo, UnusedFlag, UnusedRegisterStack, + UnusedRegisterStackInfo, + }, + binaryview::{BinaryView, BinaryViewExt}, + callingconvention::{register_calling_convention, CallingConventionBase, ConventionBuilder}, + custombinaryview::{BinaryViewType, BinaryViewTypeExt}, + disassembly::{InstructionTextToken, InstructionTextTokenContents}, + function::Function, + functionrecognizer::FunctionRecognizer, + llil, + llil::{ + ExprInfo, InstrInfo, Label, Liftable, LiftableWithSize, LiftedNonSSA, Lifter, Mutable, + NonSSA, + }, + rc::Ref, + relocation::{ + CoreRelocationHandler, CustomRelocationHandlerHandle, RelocationHandler, RelocationInfo, + RelocationType, + }, + string::BnString, + symbol::{Symbol, SymbolType}, + types::{max_confidence, min_confidence, Conf, NameAndType, Type}, +}; + +use riscv_dis::{ + FloatReg, FloatRegType, Instr, IntRegType, Op, RegFile, Register as RiscVRegister, + RiscVDisassembler, RoundMode, +}; + +enum RegType { + Integer(u32), + Float(u32), +} + +#[repr(u32)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum Intrinsic { + Uret, + Sret, + Mret, + Wfi, + Csrrw, + Csrwr, + Csrrd, + Csrrs, + Csrrc, + Fadd(u8, RoundMode), + Fsub(u8, RoundMode), + Fmul(u8, RoundMode), + Fdiv(u8, RoundMode), + Fsqrt(u8, RoundMode), + Fsgnj(u8), + Fsgnjn(u8), + Fsgnjx(u8), + Fmin(u8), + Fmax(u8), + Fclass(u8), + FcvtFToF(u8, u8, RoundMode), + FcvtIToF(u8, u8, RoundMode), + FcvtFToI(u8, u8, RoundMode), + FcvtUToF(u8, u8, RoundMode), + FcvtFToU(u8, u8, RoundMode), + Fence, +} + +#[derive(Copy, Clone)] +struct Register { + id: u32, + _dis: PhantomData, +} + +#[derive(Copy, Clone)] +struct RiscVIntrinsic { + id: Intrinsic, + _dis: PhantomData, +} + +impl Register { + fn new(id: u32) -> Self { + Self { + id, + _dis: PhantomData, + } + } + + fn reg_type(&self) -> RegType { + let int_reg_count = ::int_reg_count(); + + if self.id < int_reg_count { + RegType::Integer(self.id) + } else { + RegType::Float(self.id - int_reg_count) + } + } +} + +impl From> for Register { + fn from(reg: riscv_dis::IntReg) -> Self { + Self { + id: reg.id(), + _dis: PhantomData, + } + } +} + +impl From> for Register { + fn from(reg: FloatReg) -> Self { + let int_reg_count = ::int_reg_count(); + + Self { + id: reg.id() + int_reg_count, + _dis: PhantomData, + } + } +} + +impl Into>> for Register { + fn into(self) -> llil::Register> { + llil::Register::ArchReg(self) + } +} + +impl RegisterInfo for Register { + type RegType = Self; + + fn parent(&self) -> Option { + None + } + + fn size(&self) -> usize { + match self.reg_type() { + RegType::Integer(_) => ::Int::width(), + RegType::Float(_) => ::Float::width(), + } + } + + fn offset(&self) -> usize { + 0 + } + fn implicit_extend(&self) -> ImplicitRegisterExtend { + ImplicitRegisterExtend::NoExtend + } +} + +impl architecture::Register for Register { + type InfoType = Self; + + fn name(&self) -> Cow { + match self.reg_type() { + RegType::Integer(id) => match id { + 0 => "zero".into(), + 1 => "ra".into(), + 2 => "sp".into(), + 3 => "gp".into(), + 4 => "tp".into(), + r @ 5..=7 => format!("t{}", r - 5).into(), + r @ 8..=9 => format!("s{}", r - 8).into(), + r @ 10..=17 => format!("a{}", r - 10).into(), + r @ 18..=27 => format!("s{}", r - 16).into(), + r @ 28..=31 => format!("t{}", r - 25).into(), + _ => unreachable!(), + }, + RegType::Float(id) => match id { + r @ 0..=7 => format!("ft{}", r).into(), + r @ 8..=9 => format!("fs{}", r - 8).into(), + r @ 10..=17 => format!("fa{}", r - 10).into(), + r @ 18..=27 => format!("fs{}", r - 16).into(), + r @ 28..=31 => format!("ft{}", r - 20).into(), + _ => unreachable!(), + }, + } + } + + fn info(&self) -> Self { + *self + } + + fn id(&self) -> u32 { + self.id + } +} + +impl<'a, D: 'static + RiscVDisassembler + Send + Sync> Liftable<'a, RiscVArch> for Register { + type Result = llil::ValueExpr; + + fn lift( + il: &'a llil::Lifter>, + reg: Self, + ) -> llil::Expression<'a, RiscVArch, Mutable, NonSSA, Self::Result> { + match reg.reg_type() { + RegType::Integer(0) => il.const_int(reg.size(), 0), + RegType::Integer(_) => il.reg(reg.size(), reg), + _ => il.unimplemented(), + } + } +} + +impl<'a, D: 'static + RiscVDisassembler + Send + Sync> LiftableWithSize<'a, RiscVArch> + for Register +{ + fn lift_with_size( + il: &'a llil::Lifter>, + reg: Self, + size: usize, + ) -> llil::Expression<'a, RiscVArch, Mutable, NonSSA, llil::ValueExpr> { + #[cfg(debug_assertions)] + { + if reg.size() < size { + log::warn!( + "il @ {:x} attempted to lift {} byte register as {} byte expr", + il.current_address(), + reg.size(), + size + ); + } + } + + match reg.reg_type() { + RegType::Integer(0) => il.const_int(size, 0), + RegType::Integer(_) => { + let expr = il.reg(reg.size(), reg); + + if size < reg.size() { + il.low_part(size, expr).build() + } else { + expr + } + } + _ => il.unimplemented(), + } + } +} + +impl fmt::Debug for Register { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(self.name().as_ref()) + } +} + +impl RiscVIntrinsic { + fn id_from_parts(id: u32, sz1: Option, sz2: Option, rm: Option) -> u32 { + let sz1 = sz1.unwrap_or(0); + let sz2 = sz2.unwrap_or(0); + let rm = match rm { + None | Some(RoundMode::Dynamic) => 0, + Some(RoundMode::RoundNearestEven) => 1, + Some(RoundMode::RoundTowardZero) => 2, + Some(RoundMode::RoundDown) => 3, + Some(RoundMode::RoundUp) => 4, + Some(RoundMode::RoundMaxMagnitude) => 5, + }; + + let mut id = id << 20; + id |= sz1 as u32; + id |= (sz2 as u32) << 8; + id |= (rm as u32) << 16; + id + } + + fn parts_from_id(id: u32) -> Option<(u32, u8, u8, RoundMode)> { + let sz1 = (id & 0xff) as u8; + let sz2 = ((id >> 8) & 0xff) as u8; + let rm = match (id >> 16) & 0xf { + 0 => RoundMode::Dynamic, + 1 => RoundMode::RoundNearestEven, + 2 => RoundMode::RoundTowardZero, + 3 => RoundMode::RoundDown, + 4 => RoundMode::RoundUp, + 5 => RoundMode::RoundMaxMagnitude, + _ => return None, + }; + Some(((id >> 20) & 0xfff, sz1, sz2, rm)) + } + + fn from_id(id: u32) -> Option> { + match Self::parts_from_id(id) { + Some((0, _, _, _)) => Some(Intrinsic::Uret.into()), + Some((1, _, _, _)) => Some(Intrinsic::Sret.into()), + Some((2, _, _, _)) => Some(Intrinsic::Mret.into()), + Some((3, _, _, _)) => Some(Intrinsic::Wfi.into()), + Some((4, _, _, _)) => Some(Intrinsic::Csrrw.into()), + Some((5, _, _, _)) => Some(Intrinsic::Csrwr.into()), + Some((6, _, _, _)) => Some(Intrinsic::Csrrd.into()), + Some((7, _, _, _)) => Some(Intrinsic::Csrrs.into()), + Some((8, _, _, _)) => Some(Intrinsic::Csrrc.into()), + Some((9, size, _, rm)) => Some(Intrinsic::Fadd(size, rm).into()), + Some((10, size, _, rm)) => Some(Intrinsic::Fsub(size, rm).into()), + Some((11, size, _, rm)) => Some(Intrinsic::Fmul(size, rm).into()), + Some((12, size, _, rm)) => Some(Intrinsic::Fdiv(size, rm).into()), + Some((13, size, _, rm)) => Some(Intrinsic::Fsqrt(size, rm).into()), + Some((14, size, _, _)) => Some(Intrinsic::Fsgnj(size).into()), + Some((15, size, _, _)) => Some(Intrinsic::Fsgnjn(size).into()), + Some((16, size, _, _)) => Some(Intrinsic::Fsgnjx(size).into()), + Some((17, size, _, _)) => Some(Intrinsic::Fmin(size).into()), + Some((18, size, _, _)) => Some(Intrinsic::Fmax(size).into()), + Some((19, size, _, _)) => Some(Intrinsic::Fclass(size).into()), + Some((20, ssize, dsize, rm)) => Some(Intrinsic::FcvtFToF(ssize, dsize, rm).into()), + Some((21, isize, fsize, rm)) => Some(Intrinsic::FcvtIToF(isize, fsize, rm).into()), + Some((22, fsize, isize, rm)) => Some(Intrinsic::FcvtFToI(fsize, isize, rm).into()), + Some((23, usize, fsize, rm)) => Some(Intrinsic::FcvtUToF(usize, fsize, rm).into()), + Some((24, fsize, usize, rm)) => Some(Intrinsic::FcvtFToU(fsize, usize, rm).into()), + Some((25, _, _, _)) => Some(Intrinsic::Fence.into()), + _ => None, + } + } + + fn int_size_suffix(size: u8) -> &'static str { + match size { + 4 => "_i32", + 8 => "_i64", + _ => unreachable!(), + } + } + + fn uint_size_suffix(size: u8) -> &'static str { + match size { + 4 => "_u32", + 8 => "_u64", + _ => unreachable!(), + } + } + + fn float_size_suffix(size: u8) -> &'static str { + match size { + 4 => "_s", + 8 => "_d", + 16 => "_q", + _ => unreachable!(), + } + } + + fn round_mode_suffix(rm: RoundMode) -> &'static str { + match rm { + RoundMode::RoundNearestEven => "_rne", + RoundMode::RoundTowardZero => "_rtz", + RoundMode::RoundDown => "_rdn", + RoundMode::RoundUp => "_rup", + RoundMode::RoundMaxMagnitude => "_rmm", + RoundMode::Dynamic => "", + } + } +} + +impl From for RiscVIntrinsic { + fn from(id: Intrinsic) -> Self { + Self { + id, + _dis: PhantomData, + } + } +} + +impl architecture::Intrinsic for RiscVIntrinsic { + fn name(&self) -> Cow { + match self.id { + Intrinsic::Uret => "_uret".into(), + Intrinsic::Sret => "_sret".into(), + Intrinsic::Mret => "_mret".into(), + Intrinsic::Wfi => "_wfi".into(), + Intrinsic::Csrrw => "_csrrw".into(), + Intrinsic::Csrwr => "_csrwr".into(), + Intrinsic::Csrrd => "_csrrd".into(), + Intrinsic::Csrrs => "_csrrs".into(), + Intrinsic::Csrrc => "_csrrc".into(), + Intrinsic::Fadd(size, rm) => format!( + "_fadd{}{}", + Self::float_size_suffix(size), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::Fsub(size, rm) => format!( + "_fsub{}{}", + Self::float_size_suffix(size), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::Fmul(size, rm) => format!( + "_fmul{}{}", + Self::float_size_suffix(size), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::Fdiv(size, rm) => format!( + "_fdiv{}{}", + Self::float_size_suffix(size), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::Fsqrt(size, rm) => format!( + "_fsqrt{}{}", + Self::float_size_suffix(size), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::Fsgnj(size) => format!("_fsgnj{}", Self::float_size_suffix(size)).into(), + Intrinsic::Fsgnjn(size) => format!("_fsgnjn{}", Self::float_size_suffix(size)).into(), + Intrinsic::Fsgnjx(size) => format!("_fsgnjx{}", Self::float_size_suffix(size)).into(), + Intrinsic::Fmin(size) => format!("_fmin{}", Self::float_size_suffix(size)).into(), + Intrinsic::Fmax(size) => format!("_fmax{}", Self::float_size_suffix(size)).into(), + Intrinsic::Fclass(size) => format!("_fclass{}", Self::float_size_suffix(size)).into(), + Intrinsic::FcvtFToF(usize, fsize, rm) => format!( + "_fcvt{}_to{}{}", + Self::float_size_suffix(usize), + Self::float_size_suffix(fsize), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::FcvtIToF(isize, fsize, rm) => format!( + "_fcvt{}_to{}{}", + Self::int_size_suffix(isize), + Self::float_size_suffix(fsize), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::FcvtFToI(fsize, isize, rm) => format!( + "_fcvt{}_to{}{}", + Self::float_size_suffix(fsize), + Self::int_size_suffix(isize), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::FcvtUToF(usize, fsize, rm) => format!( + "_fcvt{}_to{}{}", + Self::uint_size_suffix(usize), + Self::float_size_suffix(fsize), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::FcvtFToU(fsize, usize, rm) => format!( + "_fcvt{}_to{}{}", + Self::float_size_suffix(fsize), + Self::uint_size_suffix(usize), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::Fence => "_fence".into(), + } + } + + fn id(&self) -> u32 { + match self.id { + Intrinsic::Uret => Self::id_from_parts(0, None, None, None), + Intrinsic::Sret => Self::id_from_parts(1, None, None, None), + Intrinsic::Mret => Self::id_from_parts(2, None, None, None), + Intrinsic::Wfi => Self::id_from_parts(3, None, None, None), + Intrinsic::Csrrw => Self::id_from_parts(4, None, None, None), + Intrinsic::Csrwr => Self::id_from_parts(5, None, None, None), + Intrinsic::Csrrd => Self::id_from_parts(6, None, None, None), + Intrinsic::Csrrs => Self::id_from_parts(7, None, None, None), + Intrinsic::Csrrc => Self::id_from_parts(8, None, None, None), + Intrinsic::Fadd(size, rm) => Self::id_from_parts(9, Some(size), None, Some(rm)), + Intrinsic::Fsub(size, rm) => Self::id_from_parts(10, Some(size), None, Some(rm)), + Intrinsic::Fmul(size, rm) => Self::id_from_parts(11, Some(size), None, Some(rm)), + Intrinsic::Fdiv(size, rm) => Self::id_from_parts(12, Some(size), None, Some(rm)), + Intrinsic::Fsqrt(size, rm) => Self::id_from_parts(13, Some(size), None, Some(rm)), + Intrinsic::Fsgnj(size) => Self::id_from_parts(14, Some(size), None, None), + Intrinsic::Fsgnjn(size) => Self::id_from_parts(15, Some(size), None, None), + Intrinsic::Fsgnjx(size) => Self::id_from_parts(16, Some(size), None, None), + Intrinsic::Fmin(size) => Self::id_from_parts(17, Some(size), None, None), + Intrinsic::Fmax(size) => Self::id_from_parts(18, Some(size), None, None), + Intrinsic::Fclass(size) => Self::id_from_parts(19, Some(size), None, None), + Intrinsic::FcvtFToF(ssize, dsize, rm) => { + Self::id_from_parts(20, Some(ssize), Some(dsize), Some(rm)) + } + Intrinsic::FcvtIToF(isize, fsize, rm) => { + Self::id_from_parts(21, Some(isize), Some(fsize), Some(rm)) + } + Intrinsic::FcvtFToI(fsize, isize, rm) => { + Self::id_from_parts(22, Some(isize), Some(fsize), Some(rm)) + } + Intrinsic::FcvtUToF(usize, fsize, rm) => { + Self::id_from_parts(23, Some(usize), Some(fsize), Some(rm)) + } + Intrinsic::FcvtFToU(fsize, usize, rm) => { + Self::id_from_parts(24, Some(usize), Some(fsize), Some(rm)) + } + Intrinsic::Fence => Self::id_from_parts(25, None, None, None), + } + } + + fn inputs(&self) -> Vec> { + match self.id { + Intrinsic::Uret | Intrinsic::Sret | Intrinsic::Mret | Intrinsic::Wfi => { + vec![] + } + Intrinsic::Csrrd => { + vec![NameAndType::new( + "csr".into(), + &Type::int(4, false), + max_confidence(), + )] + } + Intrinsic::Csrrw | Intrinsic::Csrwr | Intrinsic::Csrrs | Intrinsic::Csrrc => { + vec![ + NameAndType::new("csr".into(), &Type::int(4, false), max_confidence()), + NameAndType::new( + "value".into(), + &Type::int(::Int::width(), false), + min_confidence(), + ), + ] + } + Intrinsic::Fadd(size, _) + | Intrinsic::Fsub(size, _) + | Intrinsic::Fmul(size, _) + | Intrinsic::Fdiv(size, _) + | Intrinsic::Fsgnj(size) + | Intrinsic::Fsgnjn(size) + | Intrinsic::Fsgnjx(size) + | Intrinsic::Fmin(size) + | Intrinsic::Fmax(size) => { + vec![ + NameAndType::new("".into(), &Type::float(size as usize), max_confidence()), + NameAndType::new("".into(), &Type::float(size as usize), max_confidence()), + ] + } + Intrinsic::Fsqrt(size, _) + | Intrinsic::Fclass(size) + | Intrinsic::FcvtFToF(size, _, _) + | Intrinsic::FcvtFToI(size, _, _) + | Intrinsic::FcvtFToU(size, _, _) => { + vec![NameAndType::new( + "".into(), + &Type::float(size as usize), + max_confidence(), + )] + } + Intrinsic::FcvtIToF(size, _, _) => { + vec![NameAndType::new( + "".into(), + &Type::int(size as usize, true), + max_confidence(), + )] + } + Intrinsic::FcvtUToF(size, _, _) => { + vec![NameAndType::new( + "".into(), + &Type::int(size as usize, false), + max_confidence(), + )] + } + Intrinsic::Fence => { + vec![NameAndType::new( + "".into(), + &Type::int(4, false), + min_confidence(), + )] + } + } + } + + fn outputs(&self) -> Vec>> { + match self.id { + Intrinsic::Uret + | Intrinsic::Sret + | Intrinsic::Mret + | Intrinsic::Wfi + | Intrinsic::Csrwr + | Intrinsic::Fence => { + vec![] + } + Intrinsic::Csrrw | Intrinsic::Csrrd | Intrinsic::Csrrs | Intrinsic::Csrrc => { + vec![Conf::new( + Type::int(::Int::width(), false), + min_confidence(), + )] + } + Intrinsic::Fadd(size, _) + | Intrinsic::Fsub(size, _) + | Intrinsic::Fmul(size, _) + | Intrinsic::Fdiv(size, _) + | Intrinsic::Fsqrt(size, _) + | Intrinsic::Fsgnj(size) + | Intrinsic::Fsgnjn(size) + | Intrinsic::Fsgnjx(size) + | Intrinsic::Fmin(size) + | Intrinsic::Fmax(size) + | Intrinsic::FcvtFToF(_, size, _) + | Intrinsic::FcvtIToF(_, size, _) + | Intrinsic::FcvtUToF(_, size, _) => { + vec![Conf::new(Type::float(size as usize), max_confidence())] + } + Intrinsic::Fclass(_) => { + vec![Conf::new(Type::int(4, false), min_confidence())] + } + Intrinsic::FcvtFToI(_, size, _) => { + vec![Conf::new(Type::int(size as usize, true), max_confidence())] + } + Intrinsic::FcvtFToU(_, size, _) => { + vec![Conf::new(Type::int(size as usize, false), max_confidence())] + } + } + } +} + +struct RiscVArch { + handle: CoreArchitecture, + custom_handle: CustomArchitectureHandle>, + _dis: PhantomData, +} + +impl architecture::Architecture for RiscVArch { + type Handle = CustomArchitectureHandle; + + type RegisterInfo = Register; + type Register = Register; + type RegisterStackInfo = UnusedRegisterStackInfo; + type RegisterStack = UnusedRegisterStack; + + type Flag = UnusedFlag; + type FlagWrite = UnusedFlag; + type FlagClass = UnusedFlag; + type FlagGroup = UnusedFlag; + + type Intrinsic = RiscVIntrinsic; + + fn endianness(&self) -> binaryninja::Endianness { + binaryninja::Endianness::LittleEndian + } + + fn address_size(&self) -> usize { + ::Int::width() + } + + fn default_integer_size(&self) -> usize { + ::Int::width() + } + + fn instruction_alignment(&self) -> usize { + use riscv_dis::StandardExtension; + + if D::CompressedExtension::supported() { + 2 + } else { + 4 + } + } + + fn max_instr_len(&self) -> usize { + 4 + } + + fn opcode_display_len(&self) -> usize { + self.max_instr_len() + } + + fn associated_arch_by_addr(&self, _addr: &mut u64) -> CoreArchitecture { + self.handle + } + + fn instruction_info(&self, data: &[u8], addr: u64) -> Option { + use architecture::BranchInfo; + + let (inst_len, op) = match D::decode(addr, data) { + Ok(Instr::Rv16(op)) => (2, op), + Ok(Instr::Rv32(op)) => (4, op), + _ => return None, + }; + + let mut res = InstructionInfo::new(inst_len, false); + + match op { + Op::Jal(ref j) => { + let target = addr.wrapping_add(j.imm() as i64 as u64); + + let branch = if j.rd().id() == 0 { + BranchInfo::Unconditional(target) + } else { + BranchInfo::Call(target) + }; + + res.add_branch(branch, None); + } + Op::Jalr(ref i) => { + // TODO handle the calls with rs1 == 0? + if i.rd().id() == 0 { + let branch_type = if i.rs1().id() == 1 { + BranchInfo::FunctionReturn + } else { + BranchInfo::Unresolved + }; + + res.add_branch(branch_type, None); + } + } + Op::Beq(ref b) + | Op::Bne(ref b) + | Op::Blt(ref b) + | Op::Bge(ref b) + | Op::BltU(ref b) + | Op::BgeU(ref b) => { + res.add_branch(BranchInfo::False(addr.wrapping_add(inst_len as u64)), None); + res.add_branch( + BranchInfo::True(addr.wrapping_add(b.imm() as i64 as u64)), + None, + ); + } + Op::Ecall => { + res.add_branch(BranchInfo::SystemCall, None); + } + Op::Ebreak => { + // TODO is this valid, or should lifting handle this? + res.add_branch(BranchInfo::Unresolved, None); + } + Op::Uret | Op::Sret | Op::Mret => { + res.add_branch(BranchInfo::FunctionReturn, None); + } + _ => {} + } + + Some(res) + } + + fn instruction_text( + &self, + data: &[u8], + addr: u64, + ) -> Option<(usize, Vec)> { + use riscv_dis::Operand; + use InstructionTextTokenContents::*; + + let inst = match D::decode(addr, data) { + Ok(i) => i, + _ => return None, + }; + + let (inst_len, op) = match inst { + Instr::Rv16(op) => (2, op), + Instr::Rv32(op) => (4, op), + }; + + let mut res = Vec::new(); + let mut mnem = format!("{}", inst.mnem()); + let mut pad_len = 8usize.saturating_sub(mnem.len()); + let mut operands = inst.operands(); + + // Handle pseudo-instructions. Only single instruction pseudo-instructions are handled. + match op { + Op::AddI(i) => { + // addi zero, zero, 0 => nop + if i.rd().id() == 0 && i.rs1().id() == 0 && i.imm() == 0 { + mnem = "nop".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.clear(); + } + // addi rd, zero, imm => li rd, imm + else if i.rs1().id() == 0 { + mnem = "li".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(1); + } + // addi rd, rs, 0 => mv rd, rs + else if i.imm() == 0 { + mnem = "mv".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + } + } + Op::AddIW(i) => { + // addiw rd, rs, 0 => sext.w rd, rs + if i.imm() == 0 { + mnem = "sext.w".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + } + } + Op::Beq(i) => { + // beq rs, zero, offset => beqz rs, offset + if i.rs2().id() == 0 { + mnem = "beqz".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(1); + } + } + Op::Bne(i) => { + // bne rs, zero, offset => bnez rs, offset + if i.rs2().id() == 0 { + mnem = "bnez".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(1); + } + } + Op::Bge(i) => { + // bge zero, rs, offset => blez rs, offset + if i.rs1().id() == 0 { + mnem = "blez".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(0); + } + // bge rs, zero, offset => bgez rs, offset + else if i.rs2().id() == 0 { + mnem = "bgez".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(1); + } + } + Op::Blt(i) => { + // blt zero, rs, offset => bgtz rs, offset + if i.rs1().id() == 0 { + mnem = "bgtz".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(0); + } + // blt rs, zero, offset => bltz rs, offset + else if i.rs2().id() == 0 { + mnem = "bltz".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(1); + } + } + Op::Jal(i) => { + // jal zero, offset => j offset + if i.rd().id() == 0 { + mnem = "j".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(0); + } + // jal ra, offset => jal offset + else if i.rd().id() == 0 { + operands.remove(0); + } + } + Op::Jalr(i) => { + // jalr zero, ra, 0 => ret + if i.rd().id() == 0 && i.rs1().id() == 1 && i.imm() == 0 { + mnem = "ret".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.clear(); + } + // jalr zero, rs, 0 => jr rs + else if i.rd().id() == 0 && i.imm() == 0 { + mnem = "jr".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + operands.remove(0); + } + // jalr ra, rs, 0 => jalr rs + else if i.rd().id() == 1 && i.imm() == 0 { + mnem = "jalr".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + operands.remove(0); + } + } + Op::Slt(i) => { + // slt rd, rs, zero => sltz rd, rs + if i.rs2().id() == 0 { + mnem = "sltz".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + } + // slt rd, zero, rs => sgtz rd, rs + else if i.rs1().id() == 0 { + mnem = "sgtz".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(1); + } + } + Op::SltU(i) => { + // sltu rd, zero, rs => snez rd, rs + if i.rs1().id() == 0 { + mnem = "snez".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(1); + } + } + Op::SltIU(i) => { + // sltiu rd, rs, 1 => seqz rd, rs + if i.imm() == 1 { + mnem = "seqz".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + } + } + Op::Sub(i) => { + // sub rd, zero, rs => neg rd, rs + if i.rs1().id() == 0 { + mnem = "neg".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(1); + } + } + Op::SubW(i) => { + // subw rd, zero, rs => negw rd, rs + if i.rs1().id() == 0 { + mnem = "negw".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(1); + } + } + Op::XorI(i) => { + // xori rd, rs, -1 => not rd, rs + if i.imm() == -1 { + mnem = "not".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + } + } + Op::Fsgnj(i) => { + // fsgnj rd, rs, rs => fmv rd, rs + if i.rs1().id() == i.rs2().id() { + mnem = match i.width() { + 4 => "fmv.s".into(), + 8 => "fmv.d".into(), + 16 => "fmv.q".into(), + _ => unreachable!(), + }; + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + } + } + Op::Fsgnjn(i) => { + // fsgnjn rd, rs, rs => fneg rd, rs + if i.rs1().id() == i.rs2().id() { + mnem = match i.width() { + 4 => "fneg.s".into(), + 8 => "fneg.d".into(), + 16 => "fneg.q".into(), + _ => unreachable!(), + }; + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + } + } + Op::Fsgnjx(i) => { + // fsgnjx rd, rs, rs => fabs rd, rs + if i.rs1().id() == i.rs2().id() { + mnem = match i.width() { + 4 => "fabs.s".into(), + 8 => "fabs.d".into(), + 16 => "fabs.q".into(), + _ => unreachable!(), + }; + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + } + } + _ => (), + } + + res.push(InstructionTextToken::new(BnString::new(mnem), Instruction)); + + for (i, oper) in operands.iter().enumerate() { + if i == 0 { + res.push(InstructionTextToken::new( + BnString::new(format!("{:1$}", " ", pad_len)), + Text, + )); + } else { + res.push(InstructionTextToken::new( + BnString::new(","), + OperandSeparator, + )); + res.push(InstructionTextToken::new(BnString::new(" "), Text)); + } + + match *oper { + Operand::R(r) => { + let reg = self::Register::from(r); + + res.push(InstructionTextToken::new( + BnString::new(®.name()), + Register, + )); + } + Operand::F(r) => { + let reg = self::Register::from(r); + + res.push(InstructionTextToken::new( + BnString::new(®.name()), + Register, + )); + } + Operand::I(i) => { + match op { + Op::Beq(..) + | Op::Bne(..) + | Op::Blt(..) + | Op::Bge(..) + | Op::BltU(..) + | Op::BgeU(..) + | Op::Jal(..) => { + // BRANCH or JAL + let target = addr.wrapping_add(i as i64 as u64); + + res.push(InstructionTextToken::new( + BnString::new(format!("0x{:x}", target)), + CodeRelativeAddress(target), + )); + } + _ => { + res.push(InstructionTextToken::new( + BnString::new(match i { + -0x8_0000..=-1 => format!("-0x{:x}", -i), + _ => format!("0x{:x}", i), + }), + Integer(i as u64), + )); + } + } + } + Operand::M(i, b) => { + let reg = self::Register::from(b); + + res.push(InstructionTextToken::new( + BnString::new(""), + BeginMemoryOperand, + )); + res.push(InstructionTextToken::new( + BnString::new(if i < 0 { + format!("-0x{:x}", -i) + } else { + format!("0x{:x}", i) + }), + Integer(i as u64), + )); + + res.push(InstructionTextToken::new(BnString::new("("), Text)); + res.push(InstructionTextToken::new( + BnString::new(®.name()), + Register, + )); + res.push(InstructionTextToken::new(BnString::new(")"), Text)); + res.push(InstructionTextToken::new( + BnString::new(""), + EndMemoryOperand, + )); + } + Operand::RM(r) => { + res.push(InstructionTextToken::new(BnString::new(r.name()), Register)); + } + } + } + + Some((inst_len, res)) + } + + fn instruction_llil( + &self, + data: &[u8], + addr: u64, + il: &mut llil::Lifter, + ) -> Option<(usize, bool)> { + let max_width = self.default_integer_size(); + + let (inst_len, op) = match D::decode(addr, data) { + Ok(Instr::Rv16(op)) => (2, op), + Ok(Instr::Rv32(op)) => (4, op), + _ => return None, + }; + + macro_rules! set_reg_or_append_fallback { + ($op:ident, $t:expr, $f:expr) => {{ + let rd = Register::from($op.rd()); + match rd.id { + 0 => $f.append(), + _ => il.set_reg(rd.size(), rd, $t).append(), + } + }}; + } + + macro_rules! simple_op { + ($op:ident, no_discard $f:expr) => {{ + let expr = $f; + set_reg_or_append_fallback!($op, expr, expr) + }}; + ($op:ident, $f:expr) => { + set_reg_or_append_fallback!($op, $f, il.nop()) + }; + } + + macro_rules! simple_i { + ($i:ident, $f:expr ) => {{ + let rs1 = Register::from($i.rs1()); + simple_op!($i, $f(rs1, $i.imm())) + }}; + } + + macro_rules! simple_r { + ($r:ident, $f:expr ) => {{ + let rs1 = Register::from($r.rs1()); + let rs2 = Register::from($r.rs2()); + simple_op!($r, $f(rs1, rs2)) + }}; + } + + match op { + Op::Load(l) => simple_op!(l, no_discard { + let size = l.width(); + let rs1 = Register::from(l.rs1()); + + let src_expr = il.add(max_width, rs1, l.imm()); + let load_expr = il.load(size, src_expr) + .with_source_operand(1); + + match (size < max_width, l.zx()) { + (false, _) => load_expr, + (true, true) => il.zx(max_width, load_expr).build(), + (true, false) => il.sx(max_width, load_expr).build(), + } + }), + Op::Store(s) => { + let size = s.width(); + let dest = il.add(max_width, Register::from(s.rs1()), s.imm()); + let mut src = il + .expression(Register::from(s.rs2())) + .with_source_operand(0); + + if size < max_width { + src = il.low_part(size, src).build(); + } + + il.store(size, dest, src).with_source_operand(1).append(); + } + + Op::AddI(i) => simple_i!(i, |rs1, imm| il.add(max_width, rs1, imm)), + Op::SltI(i) => simple_i!(i, |rs1, imm| il + .bool_to_int(max_width, il.cmp_slt(max_width, rs1, imm))), + Op::SltIU(i) => simple_i!(i, |rs1, imm| il + .bool_to_int(max_width, il.cmp_ult(max_width, rs1, imm))), + Op::XorI(i) => simple_i!(i, |rs1, imm| il.xor(max_width, rs1, imm)), + Op::OrI(i) => simple_i!(i, |rs1, imm| il.or(max_width, rs1, imm)), + Op::AndI(i) => simple_i!(i, |rs1, imm| il.and(max_width, rs1, imm)), + Op::SllI(i) => simple_i!(i, |rs1, imm| il.lsl(max_width, rs1, imm)), + Op::SrlI(i) => simple_i!(i, |rs1, imm| il.lsr(max_width, rs1, imm)), + Op::SraI(i) => simple_i!(i, |rs1, imm| il.asr(max_width, rs1, imm)), + + // r-type + Op::Add(r) => simple_r!(r, |rs1, rs2| il.add(max_width, rs1, rs2)), + Op::Sll(r) => simple_r!(r, |rs1, rs2| il.lsl(max_width, rs1, rs2)), + Op::Slt(r) => simple_r!(r, |rs1, rs2| il + .bool_to_int(max_width, il.cmp_slt(max_width, rs1, rs2))), + Op::SltU(r) => simple_r!(r, |rs1, rs2| il + .bool_to_int(max_width, il.cmp_ult(max_width, rs1, rs2))), + Op::Xor(r) => simple_r!(r, |rs1, rs2| il.xor(max_width, rs1, rs2)), + Op::Srl(r) => simple_r!(r, |rs1, rs2| il.lsr(max_width, rs1, rs2)), + Op::Or(r) => simple_r!(r, |rs1, rs2| il.or(max_width, rs1, rs2)), + Op::And(r) => simple_r!(r, |rs1, rs2| il.and(max_width, rs1, rs2)), + Op::Sub(r) => simple_r!(r, |rs1, rs2| il.sub(max_width, rs1, rs2)), + Op::Sra(r) => simple_r!(r, |rs1, rs2| il.asr(max_width, rs1, rs2)), + + // i-type 32-bit + Op::AddIW(i) => simple_i!(i, |rs1, imm| il.sx(max_width, il.add(4, rs1, imm))), + Op::SllIW(i) => simple_i!(i, |rs1, imm| il.sx(max_width, il.lsl(4, rs1, imm))), + Op::SrlIW(i) => simple_i!(i, |rs1, imm| il.sx(max_width, il.lsr(4, rs1, imm))), + Op::SraIW(i) => simple_i!(i, |rs1, imm| il.sx(max_width, il.asr(4, rs1, imm))), + + // r-type 32-bit + Op::AddW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.add(4, rs1, rs2))), + Op::SllW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.lsl(4, rs1, rs2))), + Op::SrlW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.lsr(4, rs1, rs2))), + Op::SubW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.sub(4, rs1, rs2))), + Op::SraW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.asr(4, rs1, rs2))), + + Op::Mul(r) => simple_r!(r, |rs1, rs2| il.mul(max_width, rs1, rs2)), + /* + Op::MulH(r) => + Op::MulHU(r) => + Op::MulHSU(r) => + */ + Op::Div(r) => simple_r!(r, |rs1, rs2| il.divs(max_width, rs1, rs2)), + Op::DivU(r) => simple_r!(r, |rs1, rs2| il.divu(max_width, rs1, rs2)), + Op::Rem(r) => simple_r!(r, |rs1, rs2| il.mods(max_width, rs1, rs2)), + Op::RemU(r) => simple_r!(r, |rs1, rs2| il.modu(max_width, rs1, rs2)), + + Op::MulW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.mul(4, rs1, rs2))), + Op::DivW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.divs(4, rs1, rs2))), + Op::DivUW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.divu(4, rs1, rs2))), + Op::RemW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.mods(4, rs1, rs2))), + Op::RemUW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.modu(4, rs1, rs2))), + + Op::Lui(u) => simple_op!(u, il.const_int(max_width, u.imm() as i64 as u64)), + Op::Auipc(u) => simple_op!(u, il.const_ptr(addr.wrapping_add(u.imm() as i64 as u64))), + + Op::Jal(j) => { + let target = addr.wrapping_add(j.imm() as i64 as u64); + + match (j.rd().id(), il.label_for_address(target)) { + (0, Some(l)) => il.goto(l), + (0, None) => il.jump(il.const_ptr(target)), + (_, _) => il.call(il.const_ptr(target)), + } + .append(); + } + Op::Jalr(i) => { + let rd = i.rd(); + let rs1 = i.rs1(); + let imm = i.imm(); + + let target = il.add(max_width, Register::from(rs1), imm).build(); + + match (rd.id(), rs1.id(), imm) { + (0, 1, 0) => il.ret(target).append(), // jalr zero, ra, 0 + (1, _, _) => il.call(target).append(), // indirect call + (0, _, _) => il.jump(target).append(), // indirect jump + (_, _, _) => { + // indirect jump with storage of next address to non-`ra` register + il.set_reg( + max_width, + Register::from(rd), + il.const_ptr(addr.wrapping_add(inst_len)), + ) + .append(); + il.jump(target).append(); + } + } + } + + Op::Beq(b) | Op::Bne(b) | Op::Blt(b) | Op::Bge(b) | Op::BltU(b) | Op::BgeU(b) => { + let left = Register::from(b.rs1()); + let right = Register::from(b.rs2()); + + let cond_expr = match op { + Op::Beq(..) => il.cmp_e(max_width, left, right), + Op::Bne(..) => il.cmp_ne(max_width, left, right), + Op::Blt(..) => il.cmp_slt(max_width, left, right), + Op::Bge(..) => il.cmp_sge(max_width, left, right), + Op::BltU(..) => il.cmp_ult(max_width, left, right), + Op::BgeU(..) => il.cmp_uge(max_width, left, right), + _ => unreachable!(), + }; + + let mut new_false: Option