commit 38d2bc6e45496777b9be8538f640133386018119 Author: Igor kehrazy Date: Mon Apr 6 22:57:19 2026 +0300 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..46757dd --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,603 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "cranelift-bitset" +version = "0.130.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fb5b134a12b559ff0c0f5af0fcd755ad380723b5016c4e0d36f74d39485340" +dependencies = [ + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-entity" +version = "0.130.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463feed5d46cf8763f3ba3045284cf706dd161496e20ec9c14afbb4ba09b9e66" +dependencies = [ + "cranelift-bitset", + "wasmtime-internal-core", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "egg" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd40cfd4196d7a8f882ace95d623d4d6734588e502b4e188675a7c2f55eb4fb4" +dependencies = [ + "env_logger", + "hashbrown 0.15.5", + "indexmap", + "log", + "num-bigint", + "num-traits", + "quanta", + "rustc-hash", + "smallvec", + "symbol_table", + "symbolic_expressions", + "thiserror", +] + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "js-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", + "serde", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "slonik" +version = "0.1.0" +dependencies = [ + "clap", + "cranelift-entity", + "egg", + "petgraph", + "smallvec", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "symbol_table" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f19bffd69fb182e684d14e3c71d04c0ef33d1641ac0b9e81c712c734e83703bc" +dependencies = [ + "crossbeam-utils", + "foldhash 0.1.5", + "hashbrown 0.15.5", +] + +[[package]] +name = "symbolic_expressions" +version = "5.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c68d531d83ec6c531150584c42a4290911964d5f0d79132b193b67252a23b71" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasmtime-internal-core" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e671917bb6856ae360cb59d7aaf26f1cfd042c7b924319dd06fd380739fc0b2e" +dependencies = [ + "hashbrown 0.16.1", + "libm", +] + +[[package]] +name = "web-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[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" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4e8a412 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "slonik" +version = "0.1.0" +edition = "2024" + +[dependencies] +clap = { version = "4.6.0", features = ["derive"] } +cranelift-entity = "0.130.0" +egg = "0.11.0" +petgraph = "0.8.3" +smallvec = "1.15.1" diff --git a/src/arch.rs b/src/arch.rs new file mode 100644 index 0000000..75a29ac --- /dev/null +++ b/src/arch.rs @@ -0,0 +1,30 @@ +// pub trait Architecture { +// type Mode: Copy + Eq + Send + Sync + 'static; +// type DecodedInstruction: Send + Sync + 'static; + +// fn name(&self) -> &'static str; + +// fn decode( +// &self, +// bytes: &[u8], +// addr: u64, +// mode: Self::Mode, +// ) -> Result<(usize, Self::DecodedInstruction), DecodeError>; + +// fn lift( +// &self, +// insn: &Self::DecodedInstruction, +// addr: u64, +// mode: Self::Mode, +// b: &mut Builder, +// ) -> Result; + +// fn registers(&self) -> &'static [RegisterDesc]; +// fn flags(&self) -> &'static [FlagDesc]; +// } + +// pub struct InsnInfo { +// pub len: u8, +// pub branches: smallvec::SmallVec<[Branch; 2]>, +// pub terminates_block: bool, +// } diff --git a/src/ir.rs b/src/ir.rs new file mode 100644 index 0000000..2c677f4 --- /dev/null +++ b/src/ir.rs @@ -0,0 +1,42 @@ +//! Slonik normalized SSA IR. + +mod dfg; +mod inst; +mod ty; + +pub use dfg::*; +pub use inst::*; +pub use ty::*; + +/// Defines a thin `u32` entity handle. +/// +/// Slonik uses dense numeric handles instead of pointer-heavy nodes. This keeps +/// the IR compact, stable under mutation, and easy to store in `PrimaryMap` / +/// `SecondaryMap`. +macro_rules! entity { + ( + $( + $(#[$meta:meta])* + $vis:vis struct $name:ident = $prefix:literal; + )* + ) => { + $( + $(#[$meta])* + #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] + $vis struct $name(u32); + + cranelift_entity::entity_impl!($name, $prefix); + )* + }; +} + +entity! { + /// A handle to a basic block in a `Body`. + pub struct Block = "block"; + + /// A handle to an instruction in a `Body`. + pub struct Inst = "inst"; + + /// A handle to an SSA value. + pub struct Value = "v"; +} diff --git a/src/ir/dfg.rs b/src/ir/dfg.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/ir/dfg.rs @@ -0,0 +1 @@ + diff --git a/src/ir/inst.rs b/src/ir/inst.rs new file mode 100644 index 0000000..63b25a8 --- /dev/null +++ b/src/ir/inst.rs @@ -0,0 +1,413 @@ +//! Instruction opcodes and instruction data formats for Slonik. + +use core::fmt; +use cranelift_entity::EntityList; + +use crate::ir::{Block, Inst, Type, Value}; + +/// A compact list of SSA values. +/// +/// These lists are stored in a `ListPool` owned by the data-flow graph. +pub type ValueList = EntityList; + +/// A compact list of blocks. +/// +/// This is primarily useful for generic control-flow helpers and side tables. +pub type BlockList = EntityList; + +/// A target block together with the SSA arguments passed to that block. +/// +/// Slonik IR uses block arguments instead of phi nodes. A terminator that +/// transfers control to a block also provides the values for that block's +/// parameters. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct BlockCall { + /// The destination block. + pub block: Block, + + /// The arguments passed to the destination block. + pub args: ValueList, +} + +/// Integer comparison condition codes. +/// +/// These are used by `Opcode::Icmp`. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum IntCC { + /// Equal. + Eq, + + /// Not equal. + Ne, + + /// Unsigned less than. + Ult, + + /// Unsigned less than or equal. + Ule, + + /// Unsigned greater than. + Ugt, + + /// Unsigned greater than or equal. + Uge, + + /// Signed less than. + Slt, + + /// Signed less than or equal. + Sle, + + /// Signed greater than. + Sgt, + + /// Signed greater than or equal. + Sge, +} + +impl IntCC { + /// Returns the logical inverse of this condition code. + pub const fn invert(self) -> Self { + match self { + Self::Eq => Self::Ne, + Self::Ne => Self::Eq, + Self::Ult => Self::Uge, + Self::Ule => Self::Ugt, + Self::Ugt => Self::Ule, + Self::Uge => Self::Ult, + Self::Slt => Self::Sge, + Self::Sle => Self::Sgt, + Self::Sgt => Self::Sle, + Self::Sge => Self::Slt, + } + } + + /// Returns the condition code with operands swapped. + pub const fn swap_args(self) -> Self { + match self { + Self::Eq => Self::Eq, + Self::Ne => Self::Ne, + Self::Ult => Self::Ugt, + Self::Ule => Self::Uge, + Self::Ugt => Self::Ult, + Self::Uge => Self::Ule, + Self::Slt => Self::Sgt, + Self::Sle => Self::Sge, + Self::Sgt => Self::Slt, + Self::Sge => Self::Sle, + } + } +} + +impl fmt::Display for IntCC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + Self::Eq => "eq", + Self::Ne => "ne", + Self::Ult => "ult", + Self::Ule => "ule", + Self::Ugt => "ugt", + Self::Uge => "uge", + Self::Slt => "slt", + Self::Sle => "sle", + Self::Sgt => "sgt", + Self::Sge => "sge", + }; + + f.write_str(s) + } +} + +/// The set of operations that generic analysis and decompilation passes +/// are expected to understand. +/// +/// If a frontend encounters something that does not fit naturally here, that is +/// a sign it should stay in the lifted IR longer or be lowered through a helper +/// sequence before entering this IR. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum Opcode { + Iconst, + F32const, + F64const, + Bconst, + + Zext, + Sext, + Trunc, + Bitcast, + + Iadd, + Isub, + Imul, + Udiv, + Sdiv, + Urem, + Srem, + + And, + Or, + Xor, + Not, + + Shl, + Lshr, + Ashr, + + Fadd, + Fsub, + Fmul, + Fdiv, + Fneg, + + Icmp, + Select, + + Load, + Store, + + Jump, + BrIf, + Call, + Return, +} + +impl Opcode { + /// Returns the textual name of this opcode. + pub const fn name(&self) -> &'static str { + match self { + Self::Iconst => "iconst", + Self::F32const => "f32const", + Self::F64const => "f64const", + Self::Bconst => "bconst", + + Self::Zext => "zext", + Self::Sext => "sext", + Self::Trunc => "trunc", + Self::Bitcast => "bitcast", + + Self::Iadd => "iadd", + Self::Isub => "isub", + Self::Imul => "imul", + Self::Udiv => "udiv", + Self::Sdiv => "sdiv", + Self::Urem => "urem", + Self::Srem => "srem", + + Self::And => "and", + Self::Or => "or", + Self::Xor => "xor", + Self::Not => "not", + + Self::Shl => "shl", + Self::Lshr => "lshr", + Self::Ashr => "ashr", + + Self::Fadd => "fadd", + Self::Fsub => "fsub", + Self::Fmul => "fmul", + Self::Fdiv => "fdiv", + Self::Fneg => "fneg", + + Self::Icmp => "icmp", + Self::Select => "select", + + Self::Load => "load", + Self::Store => "store", + + Self::Jump => "jump", + Self::BrIf => "br_if", + Self::Call => "call", + Self::Return => "return", + } + } + + /// Returns whether this opcode is a terminator. + pub const fn is_terminator(self) -> bool { + matches!(self, Self::Jump | Self::BrIf | Self::Return) + } + + /// Returns whether this opcode may read memory. + pub const fn may_read_memory(self) -> bool { + matches!(self, Self::Load | Self::Call) + } + + /// Returns whether this opcode may write memory. + pub const fn may_write_memory(self) -> bool { + matches!(self, Self::Store | Self::Call) + } + + /// Returns whether this opcode is side-effecting. + pub const fn has_side_effects(self) -> bool { + matches!( + self, + Self::Store | Self::Jump | Self::BrIf | Self::Call | Self::Return + ) + } +} + +impl fmt::Display for Opcode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.name()) + } +} + +/// A memory access size in bytes. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum MemSize { + S1, + S2, + S4, + S8, + S16, +} + +impl MemSize { + /// Returns the memory access width in bytes. + pub const fn bytes(self) -> u8 { + match self { + Self::S1 => 1, + Self::S2 => 2, + Self::S4 => 4, + Self::S8 => 8, + Self::S16 => 16, + } + } + + /// Returns the memory access width in bits. + pub const fn bits(self) -> u16 { + // TODO: Some machines may have different byte sizes. + (self.bytes() as u16) * 8 + } +} + +impl fmt::Display for MemSize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}B", self.bytes()) + } +} + +/// The payload of an instruction. +#[derive(Clone, Debug, PartialEq)] +pub enum InstructionData { + /// A zero-operand instruction with no embedded immediate. + Nullary { opcode: Opcode }, + + /// A constant instruction holding an `i64` immediate. + Iconst { opcode: Opcode, imm: i64 }, + + /// A constant instruction holding a 32-bit floating-point bit pattern. + /// + /// The value is stored as raw IEEE-754 bits. + F32const { opcode: Opcode, bits: u32 }, + + /// A constant instruction holding a 64-bit floating-point bit pattern. + /// + /// The value is stored as raw IEEE-754 bits. + F64const { opcode: Opcode, bits: u64 }, + + /// A boolean constant. + Bconst { opcode: Opcode, value: bool }, + + /// A one-argument instruction. + Unary { opcode: Opcode, arg: Value }, + + /// A two-argument instruction. + Binary { opcode: Opcode, args: [Value; 2] }, + + /// A three-argument instruction. + /// + /// This is primarily useful for operations like `select`. + Ternary { opcode: Opcode, args: [Value; 3] }, + + /// An integer comparison instruction. + Icmp { + opcode: Opcode, + cc: IntCC, + args: [Value; 2], + }, + + /// A load from memory. + Load { + opcode: Opcode, + addr: Value, + size: MemSize, + }, + + /// A store to memory. + Store { + opcode: Opcode, + addr: Value, + value: Value, + size: MemSize, + }, + + /// An unconditional branch to another block with block arguments. + Jump { opcode: Opcode, dst: BlockCall }, + + /// A conditional branch with explicit true/false targets. + // TODO: Introduce direct call references + BrIf { + opcode: Opcode, + cond: Value, + then_dst: BlockCall, + else_dst: BlockCall, + }, + + /// A call through a value. + Call { + opcode: Opcode, + callee: Value, + args: ValueList, + }, + + /// A return from the current body. + Return { opcode: Opcode, values: ValueList }, +} + +impl InstructionData { + /// Returns the opcode of this instruction. + pub const fn opcode(&self) -> Opcode { + match *self { + Self::Nullary { opcode } + | Self::Iconst { opcode, .. } + | Self::F32const { opcode, .. } + | Self::F64const { opcode, .. } + | Self::Bconst { opcode, .. } + | Self::Unary { opcode, .. } + | Self::Binary { opcode, .. } + | Self::Ternary { opcode, .. } + | Self::Icmp { opcode, .. } + | Self::Load { opcode, .. } + | Self::Store { opcode, .. } + | Self::Jump { opcode, .. } + | Self::BrIf { opcode, .. } + | Self::Call { opcode, .. } + | Self::Return { opcode, .. } => opcode, + } + } + + /// Returns whether this instruction is a terminator. + pub const fn is_terminator(&self) -> bool { + self.opcode().is_terminator() + } +} + +/// The definition site of an SSA value. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ValueDef { + /// A value produced as result `index` of an instruction. + Result(Inst, u16), + + /// A block parameter at position `index`. + Param(Block, u16), +} + +/// Type information attached to a value in the data-flow graph. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct ValueData { + /// The semantic type of the value. + pub ty: Type, + + /// Where the value was defined. + pub def: ValueDef, +} diff --git a/src/ir/ty.rs b/src/ir/ty.rs new file mode 100644 index 0000000..da23a2d --- /dev/null +++ b/src/ir/ty.rs @@ -0,0 +1,451 @@ +//! Compact semantic types for Slonik IR. + +use core::fmt; + +/// A semantic type in Slonik. +/// +/// `Type` describes the meaning and shape of an SSA value. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum Type { + /// The absence of a value. + /// + /// This should generally not appear as the type of an SSA `Value`. + Void, + + /// A one-bit boolean. + Bool, + + /// A signedness-agnostic integer bitvector. + Int(IntType), + + /// A floating-point scalar. + Float(FloatType), + + /// A raw bitvector with no numeric interpretation yet. + Bits(BitsType), + + /// A pointer value. + Ptr(PtrType), + + /// A fixed-length or scalable SIMD vector. + Vector(VectorType), +} + +/// A fixed-width integer type. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct IntType { + bits: u16, +} + +impl IntType { + /// Creates a new integer type with the given width in bits. + /// + /// # Panics + /// + /// Panics if `bits == 0`. + pub const fn new(bits: u16) -> Self { + assert!(bits != 0, "integer type must have nonzero width"); + Self { bits } + } + + /// Returns the integer width in bits. + pub const fn bits(self) -> u16 { + self.bits + } +} + +/// A floating-point scalar type. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum FloatType { + F16, + F32, + F64, + F80, + F128, +} + +impl FloatType { + /// Returns the width of this floating-point type in bits. + pub const fn bits(self) -> u16 { + match self { + Self::F16 => 16, + Self::F32 => 32, + Self::F64 => 64, + Self::F80 => 80, + Self::F128 => 128, + } + } +} + +/// A raw uninterpreted bitvector type. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct BitsType { + bits: u16, +} + +impl BitsType { + /// Creates a new raw bitvector type with the given width. + /// + /// # Panics + /// + /// Panics if `bits == 0`. + pub const fn new(bits: u16) -> Self { + assert!(bits != 0, "bitvector type must have nonzero width"); + Self { bits } + } + + /// Returns the width in bits. + pub const fn bits(self) -> u16 { + self.bits + } +} + +/// A pointer type. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct PtrType { + bits: u16, + addr_space: u16, +} + +impl PtrType { + /// Creates a new pointer type in address space 0. + /// + /// # Panics + /// + /// Panics if `bits == 0`. + pub const fn new(bits: u16) -> Self { + Self::with_addr_space(bits, 0) + } + + /// Creates a new pointer type with an explicit address space. + /// + /// # Panics + /// + /// Panics if `bits == 0`. + pub const fn with_addr_space(bits: u16, addr_space: u16) -> Self { + assert!(bits != 0, "pointer type must have nonzero width"); + Self { bits, addr_space } + } + + /// Returns the pointer width in bits. + pub const fn bits(self) -> u16 { + self.bits + } + + /// Returns the address space. + pub const fn addr_space(self) -> u16 { + self.addr_space + } +} + +/// A scalar lane type for a vector. +/// +/// Vector lanes are semantic scalar elements, not storage slots. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum LaneType { + Bool, + Int(IntType), + Float(FloatType), + Bits(BitsType), + Ptr(PtrType), +} + +impl LaneType { + /// Returns the width of one lane in bits. + pub const fn bits(self) -> u16 { + match self { + Self::Bool => 1, + Self::Int(ty) => ty.bits(), + Self::Float(ty) => ty.bits(), + Self::Bits(ty) => ty.bits(), + Self::Ptr(ty) => ty.bits(), + } + } + + /// Converts this lane type into the corresponding scalar `Type`. + pub const fn as_type(self) -> Type { + match self { + Self::Bool => Type::Bool, + Self::Int(ty) => Type::Int(ty), + Self::Float(ty) => Type::Float(ty), + Self::Bits(ty) => Type::Bits(ty), + Self::Ptr(ty) => Type::Ptr(ty), + } + } +} + +/// A SIMD vector type. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct VectorType { + lanes: u16, + lane: LaneType, + scalable: bool, +} + +impl VectorType { + /// Creates a new fixed-length vector type. + /// + /// # Panics + /// + /// Panics if `lanes == 0`. + pub const fn new(lane: LaneType, lanes: u16) -> Self { + assert!(lanes != 0, "vector type must have at least one lane"); + Self { + lanes, + lane, + scalable: false, + } + } + + /// Creates a new vector type with explicit scalability. + /// + /// # Panics + /// + /// Panics if `lanes == 0`. + pub const fn with_scalable(lane: LaneType, lanes: u16, scalable: bool) -> Self { + assert!(lanes != 0, "vector type must have at least one lane"); + Self { + lanes, + lane, + scalable, + } + } + + /// Returns the number of lanes. + pub const fn lanes(self) -> u16 { + self.lanes + } + + /// Returns the lane type. + pub const fn lane(self) -> LaneType { + self.lane + } + + /// Returns whether this is a scalable vector. + pub const fn scalable(self) -> bool { + self.scalable + } + + /// Returns the width of one lane in bits. + pub const fn lane_bits(self) -> u16 { + self.lane.bits() + } + + /// Returns the minimum number of bits in the vector. + /// + /// For scalable vectors, this is the known minimum width. + pub const fn min_bits(self) -> u32 { + (self.lanes as u32) * (self.lane.bits() as u32) + } +} + +impl Type { + /// Returns `void`. + pub const fn void() -> Self { + Self::Void + } + + /// Returns `bool`. + pub const fn bool() -> Self { + Self::Bool + } + + /// Returns an integer type with the given width. + pub const fn int(bits: u16) -> Self { + Self::Int(IntType::new(bits)) + } + + /// Returns a floating-point type. + pub const fn float(kind: FloatType) -> Self { + Self::Float(kind) + } + + /// Returns a raw bitvector type with the given width. + pub const fn bits(bits: u16) -> Self { + Self::Bits(BitsType::new(bits)) + } + + /// Returns a pointer type in address space 0. + pub const fn ptr(bits: u16) -> Self { + Self::Ptr(PtrType::new(bits)) + } + + /// Returns a pointer type with an explicit address space. + pub const fn ptr_in(bits: u16, addr_space: u16) -> Self { + Self::Ptr(PtrType::with_addr_space(bits, addr_space)) + } + + /// Returns a fixed-length vector type. + pub const fn vector(lane: LaneType, lanes: u16) -> Self { + Self::Vector(VectorType::new(lane, lanes)) + } + + /// Returns a vector type with explicit scalability. + pub const fn vector_with_scalable(lane: LaneType, lanes: u16, scalable: bool) -> Self { + Self::Vector(VectorType::with_scalable(lane, lanes, scalable)) + } + + /// Returns whether this is `void`. + pub const fn is_void(self) -> bool { + matches!(self, Self::Void) + } + + /// Returns whether this is `bool`. + pub const fn is_bool(self) -> bool { + matches!(self, Self::Bool) + } + + /// Returns whether this is an integer. + pub const fn is_int(self) -> bool { + matches!(self, Self::Int(_)) + } + + /// Returns whether this is a float. + pub const fn is_float(self) -> bool { + matches!(self, Self::Float(_)) + } + + /// Returns whether this is a raw bitvector. + pub const fn is_bits(self) -> bool { + matches!(self, Self::Bits(_)) + } + + /// Returns whether this is a pointer. + pub const fn is_ptr(self) -> bool { + matches!(self, Self::Ptr(_)) + } + + /// Returns whether this is a vector. + pub const fn is_vector(self) -> bool { + matches!(self, Self::Vector(_)) + } + + /// Returns whether this is any scalar type. + pub const fn is_scalar(self) -> bool { + !matches!(self, Self::Vector(_)) + } + + /// Returns the scalar width in bits, if this is a scalar type. + pub const fn bit_size(self) -> Option { + match self { + Self::Void => None, + Self::Bool => Some(1), + Self::Int(ty) => Some(ty.bits()), + Self::Float(ty) => Some(ty.bits()), + Self::Bits(ty) => Some(ty.bits()), + Self::Ptr(ty) => Some(ty.bits()), + Self::Vector(_) => None, + } + } + + /// Returns the vector description, if this is a vector. + pub const fn as_vector(self) -> Option { + match self { + Self::Vector(ty) => Some(ty), + _ => None, + } + } + + /// Returns the scalar lane type if this is a vector, otherwise `None`. + pub const fn lane_type(self) -> Option { + match self { + Self::Vector(ty) => Some(ty.lane()), + _ => None, + } + } + + /// Returns the number of lanes if this is a vector. + pub const fn lanes(self) -> Option { + match self { + Self::Vector(ty) => Some(ty.lanes()), + _ => None, + } + } + + /// Convenience constructor for `i8`. + pub const fn i8() -> Self { + Self::int(8) + } + + /// Convenience constructor for `i16`. + pub const fn i16() -> Self { + Self::int(16) + } + + /// Convenience constructor for `i32`. + pub const fn i32() -> Self { + Self::int(32) + } + + /// Convenience constructor for `i64`. + pub const fn i64() -> Self { + Self::int(64) + } + + /// Convenience constructor for `i128`. + pub const fn i128() -> Self { + Self::int(128) + } + + /// Convenience constructor for `f32`. + pub const fn f32() -> Self { + Self::float(FloatType::F32) + } + + /// Convenience constructor for `f64`. + pub const fn f64() -> Self { + Self::float(FloatType::F64) + } +} + +impl fmt::Display for Type { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Type::Void => f.write_str("void"), + Type::Bool => f.write_str("bool"), + Type::Int(ty) => write!(f, "i{}", ty.bits()), + Type::Float(FloatType::F16) => f.write_str("f16"), + Type::Float(FloatType::F32) => f.write_str("f32"), + Type::Float(FloatType::F64) => f.write_str("f64"), + Type::Float(FloatType::F80) => f.write_str("f80"), + Type::Float(FloatType::F128) => f.write_str("f128"), + Type::Bits(ty) => write!(f, "b{}", ty.bits()), + Type::Ptr(ty) => { + if ty.addr_space() == 0 { + write!(f, "ptr{}", ty.bits()) + } else { + write!(f, "ptr{}@{}", ty.bits(), ty.addr_space()) + } + } + Type::Vector(ty) => { + if ty.scalable() { + write!(f, "vec", ty.lanes(), ty.lane()) + } else { + write!(f, "vec<{} x {}>", ty.lanes(), ty.lane()) + } + } + } + } +} + +impl fmt::Display for LaneType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + LaneType::Bool => f.write_str("bool"), + LaneType::Int(ty) => write!(f, "i{}", ty.bits()), + LaneType::Float(FloatType::F16) => f.write_str("f16"), + LaneType::Float(FloatType::F32) => f.write_str("f32"), + LaneType::Float(FloatType::F64) => f.write_str("f64"), + LaneType::Float(FloatType::F80) => f.write_str("f80"), + LaneType::Float(FloatType::F128) => f.write_str("f128"), + LaneType::Bits(ty) => write!(f, "b{}", ty.bits()), + LaneType::Ptr(ty) => { + if ty.addr_space() == 0 { + write!(f, "ptr{}", ty.bits()) + } else { + write!(f, "ptr{}@{}", ty.bits(), ty.addr_space()) + } + } + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5f470be --- /dev/null +++ b/src/main.rs @@ -0,0 +1,27 @@ +mod arch; +mod ir; + +use clap::{ + Parser, + builder::{ + Styles, + styling::{AnsiColor, Effects}, + }, +}; + +const STYLES: Styles = Styles::styled() + .header(AnsiColor::Green.on_default().effects(Effects::BOLD)) + .usage(AnsiColor::Green.on_default().effects(Effects::BOLD)) + .literal(AnsiColor::Cyan.on_default().effects(Effects::BOLD)) + .placeholder(AnsiColor::Cyan.on_default()); + +#[derive(Parser)] +#[command(name = "slonik", author)] +#[command(about = "an experimental decompiler")] +#[command(disable_version_flag = true, arg_required_else_help = true)] +#[command(styles=STYLES)] +struct Args {} + +fn main() { + let args = Args::parse(); +} diff --git a/src/x86.rs b/src/x86.rs new file mode 100644 index 0000000..ceecf47 --- /dev/null +++ b/src/x86.rs @@ -0,0 +1 @@ +mod state; diff --git a/src/x86/state.rs b/src/x86/state.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/x86/state.rs @@ -0,0 +1 @@ +