pax_global_header00006660000000000000000000000064137423613010014512gustar00rootroot0000000000000052 comment=0ed2b8005bf9c0dfaec6a2136f57704e78d4ce4a souper-ir-2.1.0/000077500000000000000000000000001374236130100134375ustar00rootroot00000000000000souper-ir-2.1.0/.README.tpl000066400000000000000000000000321374236130100151660ustar00rootroot00000000000000# `{{crate}}` {{readme}} souper-ir-2.1.0/.gitattributes000066400000000000000000000001321374236130100163260ustar00rootroot00000000000000# The README.md is programmatically generated with `cargo readme`. README.md -diff -merge souper-ir-2.1.0/.github/000077500000000000000000000000001374236130100147775ustar00rootroot00000000000000souper-ir-2.1.0/.github/workflows/000077500000000000000000000000001374236130100170345ustar00rootroot00000000000000souper-ir-2.1.0/.github/workflows/main.yml000066400000000000000000000025721374236130100205110ustar00rootroot00000000000000name: CI on: push: branches: [ main ] pull_request: branches: [ main ] env: CARGO_TERM_COLOR: always jobs: fuzz: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - run: cargo install cargo-fuzz - run: cargo fuzz build --sanitizer none --dev build: strategy: matrix: features: ["", "parse", "stringify", "parse stringify"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Build run: cargo build --verbose --features "${{ matrix.features }}" - name: Run tests run: cargo test --verbose --features "${{ matrix.features }}" tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: submodules: true - run: cargo build --verbose -p souper-ir-tests - run: cargo test --verbose -p souper-ir-tests readme: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - run: cargo install cargo-readme - run: cargo readme -t .README.tpl > README.md - run: | git diff --exit-code || { echo echo "============================================================" echo echo "README.md is not up to date! Run" echo echo " $ cargo readme -t .README.tpl > README.md" echo echo "to update it." exit 1 } souper-ir-2.1.0/.gitignore000066400000000000000000000000231374236130100154220ustar00rootroot00000000000000/target Cargo.lock souper-ir-2.1.0/.gitmodules000066400000000000000000000001431374236130100156120ustar00rootroot00000000000000[submodule "tests/souper"] path = crates/tests/souper url = https://github.com/google/souper.git souper-ir-2.1.0/Cargo.toml000066400000000000000000000011261374236130100153670ustar00rootroot00000000000000[package] authors = ["Nick Fitzgerald "] edition = "2018" description = "A library for manipulating Souper IR" documentation = "https://docs.rs/souper-ir" license = "MIT OR Apache-2.0" name = "souper-ir" readme = "./README.md" repository = "https://github.com/fitzgen/souper-ir" version = "2.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] id-arena = "2.2.1" [features] parse = [] stringify = [] [workspace] members = [ "./crates/tests", "./fuzz", ] [package.metadata.docs.rs] all-features = true souper-ir-2.1.0/README.md000066400000000000000000000046511374236130100147240ustar00rootroot00000000000000# `souper-ir` A library for manipulating [Souper] IR. [![](https://docs.rs/souper-ir/badge.svg)](https://docs.rs/souper-ir/) [![](https://img.shields.io/crates/v/souper-ir.svg)](https://crates.io/crates/souper-ir) [![](https://img.shields.io/crates/d/souper-ir.svg)](https://crates.io/crates/souper-ir) ![CI](https://github.com/fitzgen/souper-ir/workflows/CI/badge.svg) This crate provides AST types for parsing or generating Souper IR. It is a suitable building block either for writing a custom LHS extractor, or for translating learned optimizations into your own peephole optimizations pass. ### AST The AST type definitions and builders live in the `souper_ir::ast` module. ### Parsing Souper IR When the `parse` Cargo feature is enabled, the `souper_ir::parse` module contains functions for parsing Souper IR from a file or an in-memory string. ```rust use std::path::Path; // We provide a filename to get better error messages. let filename = Path::new("example.souper"); let replacements = souper_ir::parse::parse_replacements_str(" ;; x + x --> 2 * x %0 = var %1 = add %0, %0 %2 = mul %0, 2 cand %1, %2 ;; x & x --> x %0 = var %1 = and %0, %0 cand %1, %0 ", Some(filename))?; ``` ### Emitting Souper IR's Text Format When the `stringify` Cargo feature is enabled, then the `souper_ir::ast::Replacement`, `souper_ir::ast::LeftHandSide`, and `souper_ir::ast::RightHandSide` types all implement `std::fmt::Display`. The `Display` implementation writes the AST type out as Souper's text format. ```rust use souper_ir::ast; // Build this Souper left-hand side: // // %x:i32 = var // %y = mul %x, 2 // infer %y // // We expect that Souper would be able to synthesize a right-hand side that // does a left shift by one instead of a multiplication. let mut lhs = ast::LeftHandSideBuilder::default(); let x = lhs.assignment( Some("x".into()), Some(ast::Type { width: 32 }), ast::AssignmentRhs::Var, vec![], ); let y = lhs.assignment( Some("y".into()), None, ast::Instruction::Mul { a: x.into(), b: ast::Constant { value: 2, r#type: None }.into(), }, vec![], ); let lhs = lhs.finish(y, vec![]); // Now we can stringify the LHS (and then, presumably, give it to Souper) // with `std::fmt::Display`: use std::io::Write; let mut file = std::fs::File::create("my-lhs.souper")?; write!(&mut file, "{}", lhs)?; ``` [Souper]: https://github.com/google/souper souper-ir-2.1.0/crates/000077500000000000000000000000001374236130100147205ustar00rootroot00000000000000souper-ir-2.1.0/crates/tests/000077500000000000000000000000001374236130100160625ustar00rootroot00000000000000souper-ir-2.1.0/crates/tests/Cargo.toml000066400000000000000000000004241374236130100200120ustar00rootroot00000000000000[package] name = "souper-ir-tests" version = "0.1.0" authors = ["Nick Fitzgerald "] edition = "2018" publish = false [dependencies] souper-ir = { path = "../..", features = ["parse", "stringify"] } [build-dependencies] anyhow = "1.0.31" walkdir = "2.3.1" souper-ir-2.1.0/crates/tests/build.rs000066400000000000000000000034401374236130100175300ustar00rootroot00000000000000use anyhow::Context; use std::{env, fs, io::Write, path::PathBuf}; fn main() -> anyhow::Result<()> { println!("cargo:rerun-if-changed=build.rs"); let out_dir = PathBuf::from( env::var_os("OUT_DIR").expect("The OUT_DIR environment variable must be set"), ); let souper_tests = PathBuf::from("souper").join("test"); println!("cargo:rerun-if-changed={}", souper_tests.display()); let test_file_path = out_dir.join("souper_tests.rs"); let mut out = fs::File::create(&test_file_path) .with_context(|| format!("failed to create file: {}", test_file_path.display()))?; if !souper_tests.exists() { return missing_souper_submodule(); } for entry in walkdir::WalkDir::new(souper_tests) { let entry = entry?; if entry.path().extension().map_or(false, |x| x == "opt") { println!("cargo:rerun-if-changed={}", entry.path().display()); let test_name = entry .path() .display() .to_string() .chars() .map(|c| match c { 'a'..='z' | 'A'..='Z' | '0'..='9' => c, _ => '_', }) .collect::(); writeln!( out, "\ #[test] #[allow(non_snake_case)] fn test_{}() {{ crate::assert_parse_file(std::path::Path::new(\"{}\")); }} ", test_name, entry.path().display() ) .with_context(|| format!("failed to write to file: {}", test_file_path.display()))?; } } Ok(()) } fn missing_souper_submodule() -> anyhow::Result<()> { println!( "cargo:warning=The `souper` submodule is not checked out, \ skipping some tests generation" ); Ok(()) } souper-ir-2.1.0/crates/tests/souper/000077500000000000000000000000001374236130100173775ustar00rootroot00000000000000souper-ir-2.1.0/crates/tests/src/000077500000000000000000000000001374236130100166515ustar00rootroot00000000000000souper-ir-2.1.0/crates/tests/src/lib.rs000066400000000000000000000055741374236130100200000ustar00rootroot00000000000000use std::{fmt::Write, path::Path}; pub fn assert_parse_file(path: &Path) { let s = std::fs::read_to_string(path).unwrap(); let repl = souper_ir::parse::parse_replacements_str(&s, Some(path)); let lhs = souper_ir::parse::parse_left_hand_sides_str(&s, Some(path)); let rhs = souper_ir::parse::parse_right_hand_sides_str(&s, Some(path)); assert!( repl.is_ok() || lhs.is_ok() || rhs.is_ok(), "souper test file {:?} failed to parse as a replacement, LHS, or RHS. Parse replacement error: {} Parse LHS error: {} Parse RHS error: {}", path, repl.unwrap_err().to_string(), lhs.unwrap_err().to_string(), rhs.unwrap_err().to_string(), ); // Check that we can deterministically round-trip replacements. if let Ok(repl) = repl { let mut canonical_string_1 = String::new(); for r in &repl { writeln!(&mut canonical_string_1, "{}\n", r).unwrap(); } let repl_2 = souper_ir::parse::parse_replacements_str(&canonical_string_1, Some(path)) .expect("should parse our stringified IR okay"); let mut canonical_string_2 = String::new(); for r in &repl_2 { writeln!(&mut canonical_string_2, "{}\n", r).unwrap(); } assert_eq!( canonical_string_1, canonical_string_2, "re-stringifying should generate the same string", ); } // Check that we can deterministically round-trip LHSes. if let Ok(lhs) = lhs { let mut canonical_string_1 = String::new(); for l in &lhs { writeln!(&mut canonical_string_1, "{}\n", l).unwrap(); } let lhs_2 = souper_ir::parse::parse_left_hand_sides_str(&canonical_string_1, Some(path)) .expect("should parse our stringified IR okay"); let mut canonical_string_2 = String::new(); for l in &lhs_2 { writeln!(&mut canonical_string_2, "{}\n", l).unwrap(); } assert_eq!( canonical_string_1, canonical_string_2, "re-stringifying should generate the same string", ); } // Check that we can deterministically round-trip RHSes. if let Ok(rhs) = rhs { let mut canonical_string_1 = String::new(); for r in &rhs { writeln!(&mut canonical_string_1, "{}\n", r).unwrap(); } let rhs_2 = souper_ir::parse::parse_right_hand_sides_str(&canonical_string_1, Some(path)) .expect("should parse our stringified IR okay"); let mut canonical_string_2 = String::new(); for r in &rhs_2 { writeln!(&mut canonical_string_2, "{}\n", r).unwrap(); } assert_eq!( canonical_string_1, canonical_string_2, "re-stringifying should generate the same string", ); } } #[cfg(test)] mod tests { include!(concat!(env!("OUT_DIR"), "/", "souper_tests.rs")); } souper-ir-2.1.0/fuzz/000077500000000000000000000000001374236130100144355ustar00rootroot00000000000000souper-ir-2.1.0/fuzz/.gitignore000066400000000000000000000000311374236130100164170ustar00rootroot00000000000000 target corpus artifacts souper-ir-2.1.0/fuzz/Cargo.toml000066400000000000000000000006141374236130100163660ustar00rootroot00000000000000 [package] name = "souper-ir-fuzz" version = "0.0.0" authors = ["Automatically generated"] publish = false edition = "2018" [package.metadata] cargo-fuzz = true [dependencies] libfuzzer-sys = "0.3" log = "0.4.11" env_logger = "0.7.1" [dependencies.souper-ir] path = ".." features = ["parse", "stringify"] [[bin]] name = "try_parse" path = "fuzz_targets/try_parse.rs" test = false doc = false souper-ir-2.1.0/fuzz/fuzz_targets/000077500000000000000000000000001374236130100171645ustar00rootroot00000000000000souper-ir-2.1.0/fuzz/fuzz_targets/try_parse.rs000077500000000000000000000050501374236130100215450ustar00rootroot00000000000000#![no_main] use libfuzzer_sys::fuzz_target; use souper_ir::parse::{ parse_left_hand_sides_str, parse_replacements_str, parse_right_hand_sides_str, }; use std::{fmt::Write, path::Path}; fuzz_target!(|data: &[u8]| { let _ = env_logger::try_init(); let s = match std::str::from_utf8(data) { Ok(s) => s, Err(_) => return, }; log::debug!("input string = \"\"\"\n{}\n\"\"\"", s); let filepath = Some(Path::new("fuzzer.data")); if let Ok(repl) = parse_replacements_str(s, filepath) { let mut canon1 = String::with_capacity(s.len() * 2); for r in &repl { writeln!(canon1, "{}", r).unwrap(); } let repl2 = parse_replacements_str(&canon1, filepath) .expect("should re-parse our stringified source text"); let mut canon2 = String::with_capacity(canon1.len()); for r in &repl2 { writeln!(canon2, "{}", r).unwrap(); } if canon1 != canon2 { log::debug!("canon1 = '''\n{}\n'''", canon1); log::debug!("canon2 = '''\n{}\n'''", canon2); panic!("failed to round-trip deterministically"); } } if let Ok(lhs) = parse_left_hand_sides_str(s, filepath) { let mut canon1 = String::with_capacity(s.len() * 2); for l in &lhs { writeln!(canon1, "{}", l).unwrap(); } let lhs2 = parse_left_hand_sides_str(&canon1, filepath) .expect("should re-parse our stringified source text"); let mut canon2 = String::with_capacity(canon1.len()); for l in &lhs2 { writeln!(canon2, "{}", l).unwrap(); } if canon1 != canon2 { log::debug!("canon1 = '''\n{}\n'''", canon1); log::debug!("canon2 = '''\n{}\n'''", canon2); panic!("failed to round-trip deterministically"); } } if let Ok(rhs) = parse_right_hand_sides_str(s, filepath) { let mut canon1 = String::with_capacity(s.len() * 2); for r in &rhs { writeln!(canon1, "{}", r).unwrap(); } let rhs2 = parse_right_hand_sides_str(&canon1, filepath) .expect("should re-parse our stringified source text"); let mut canon2 = String::with_capacity(canon1.len()); for r in &rhs2 { writeln!(canon2, "{}", r).unwrap(); } if canon1 != canon2 { log::debug!("canon1 = '''\n{}\n'''", canon1); log::debug!("canon2 = '''\n{}\n'''", canon2); panic!("failed to round-trip deterministically"); } } }); souper-ir-2.1.0/src/000077500000000000000000000000001374236130100142265ustar00rootroot00000000000000souper-ir-2.1.0/src/ast.rs000066400000000000000000000632741374236130100153770ustar00rootroot00000000000000//! Abstract syntax tree type definitions. pub use id_arena::{Arena, Id}; /// An identifier for a value defined by an assignment. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct ValueId( /// Always points to a `Statement::Assignment`, and references the value /// defined by the assignment. pub(crate) Id, ); impl From for Id { #[inline] fn from(id: ValueId) -> Self { id.0 } } /// An identifier for a defined block. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct BlockId( /// Always points to an assignment where the RHS is `AssignmentRhs::Block`. pub(crate) ValueId, ); impl From for ValueId { #[inline] fn from(id: BlockId) -> Self { id.0 } } impl From for Id { #[inline] fn from(id: BlockId) -> Self { (id.0).0 } } /// A complete optimization that replaces a left-hand side with a right-hand /// side. #[derive(Clone, Debug)] pub enum Replacement { /// A replacement in the form of a left-hand side followed by a right-hand /// side. LhsRhs { /// Statements that make up the expression DAGs for the left- and /// right-hand sides. statements: Arena, /// The left-hand side that is matched by the optimization. lhs: Infer, /// The right-hand side that replaces the left-hand side after applying /// the optimization. rhs: Operand, }, /// A replacement in the form of an expression DAG followed by a `cand x y` /// statement that declares that `y` is a candidate for replacing `x`. Cand { /// Statements that make up the expression DAG for both the /// replacement's left- and right-hand sides. statements: Arena, /// The candidate rewrite connecting the left- and right-hand sides of /// this replacement within `statements`. cand: Cand, }, } impl Replacement { /// Get the assignment that defined the given value. /// /// # Panics /// /// May panic or produce incorrect results if given a `ValueId` from another /// `Replacement`, `LeftHandSide`, or `RightHandSide`'s arena. pub fn assignment(&self, id: ValueId) -> &Assignment { match self { Replacement::LhsRhs { statements, .. } | Replacement::Cand { statements, .. } => { match &statements[id.into()] { Statement::Assignment(a) => a, _ => panic!("use of an `id` that is not from this `Replacement`'s arena"), } } } } } /// A builder for a [`Replacement`][crate::ast::Replacement]. #[derive(Clone, Debug, Default)] pub struct ReplacementBuilder { statements: Arena, } impl ReplacementBuilder { /// Create a new assignment statement. /// /// Returns the value defined by the assignment. /// /// # Panics /// /// Panics if `name` (when given) does not start with '%'. pub fn assignment( &mut self, name: Option, r#type: Option, value: impl Into, attributes: Vec, ) -> ValueId { let name = name.unwrap_or_else(|| format!("%{}", self.statements.len())); assert!(name.starts_with('%')); ValueId( self.statements.alloc( Assignment { name, r#type, value: value.into(), attributes, } .into(), ), ) } /// Create a new [basic block][crate::ast::Block]. /// /// Declare that the block has `predecessors` number of incoming edges in /// the control-flow graph. /// /// # Panics /// /// Panics if `name` (when given) does not start with '%'. pub fn block(&mut self, name: Option, predecessors: u32) -> BlockId { BlockId(self.assignment(name, None, Block { predecessors }, vec![])) } /// Create a [path condition][crate::ast::Pc]. /// /// Expresses the fact that `x` must equal `y` for the replacement to be /// valid. pub fn pc(&mut self, x: impl Into, y: impl Into) { let x = x.into(); let y = y.into(); self.statements.alloc(Pc { x, y }.into()); } /// Create a [block path condition][crate::ast::BlockPc]. /// /// Expresses that `x` is equal to `y` on an incoming edge to `block` in the /// control-flow graph. /// /// # Panics /// /// Panics if `predecessor` is greater than or equal to `block`'s number of /// predecessors. pub fn block_pc( &mut self, block: BlockId, predecessor: u32, x: impl Into, y: impl Into, ) { let x = x.into(); let y = y.into(); self.statements.alloc( BlockPc { block, predecessor, x, y, } .into(), ); } /// Finish building this replacement by providing the left- and right-hand /// sides. pub fn finish( self, lhs: ValueId, rhs: impl Into, attributes: impl IntoIterator, ) -> Replacement { Replacement::LhsRhs { statements: self.statements, lhs: Infer { value: lhs, attributes: attributes.into_iter().collect(), }, rhs: rhs.into(), } } } /// A candidate rewrite. #[derive(Clone, Debug)] pub struct Cand { /// The left-hand side expression that can be replaced by the right-hand /// side. pub lhs: Operand, /// The right-hand side expression that can replace the left-hand side. pub rhs: Operand, /// Attributes for this rewrite. pub attributes: Vec, } /// The left-hand side of a replacement. #[derive(Clone, Debug)] pub struct LeftHandSide { /// Statements making up this LHS's expression DAG. pub statements: Arena, /// The root of this LHS's expression DAG. pub infer: Infer, } /// A builder for a [`LeftHandSide`][crate::ast::LeftHandSide]. #[derive(Clone, Debug, Default)] pub struct LeftHandSideBuilder { statements: Arena, } impl LeftHandSideBuilder { /// Create a new assignment statement. /// /// Returns the value defined by the assignment. /// /// # Panics /// /// Panics if `name` (when given) does not start with '%'. pub fn assignment( &mut self, name: Option, r#type: Option, value: impl Into, attributes: Vec, ) -> ValueId { let name = name.unwrap_or_else(|| format!("%{}", self.statements.len())); assert!(name.starts_with('%')); ValueId( self.statements.alloc( Assignment { name, r#type, value: value.into(), attributes, } .into(), ), ) } /// Create a new [basic block][crate::ast::Block]. /// /// Declare that the block has `predecessors` number of incoming edges in /// the control-flow graph. /// /// # Panics /// /// Panics if `name` (when given) does not start with '%'. pub fn block(&mut self, name: Option, predecessors: u32) -> BlockId { BlockId(self.assignment(name, None, Block { predecessors }, vec![])) } /// Create a [path condition][crate::ast::Pc]. /// /// Expresses the fact that `x` must equal `y` for the replacement to be /// valid. pub fn pc(&mut self, x: impl Into, y: impl Into) { let x = x.into(); let y = y.into(); self.statements.alloc(Pc { x, y }.into()); } /// Create a [block path condition][crate::ast::BlockPc]. /// /// Expresses that `x` is equal to `y` on an incoming edge to `block` in the /// control-flow graph. /// /// # Panics /// /// Panics if `predecessor` is greater than or equal to `block`'s number of /// predecessors. pub fn block_pc( &mut self, block: BlockId, predecessor: u32, x: impl Into, y: impl Into, ) { let x = x.into(); let y = y.into(); self.statements.alloc( BlockPc { block, predecessor, x, y, } .into(), ); } /// Finish building this `LeftHandSide`. pub fn finish( self, lhs: ValueId, attributes: impl IntoIterator, ) -> LeftHandSide { LeftHandSide { statements: self.statements, infer: Infer { value: lhs, attributes: attributes.into_iter().collect(), }, } } /// Get the assignment that created the given value. /// /// # Panics /// /// May panic when given a `ValudId` from a different LHS, RHS, or /// replacement. pub fn get_value(&self, id: ValueId) -> &Assignment { match &self.statements[id.into()] { Statement::Assignment(a) => a, _ => panic!(), } } } /// The root of a left-hand side. #[derive(Clone, Debug)] pub struct Infer { /// The value to be replaced by the right-hand side. pub value: ValueId, /// Attributes for this left-hand side. pub attributes: Vec, } /// The right-hand side of a replacement. #[derive(Clone, Debug)] pub struct RightHandSide { /// Statements making up this RHS's expression DAG. pub statements: Arena, /// The root of this RHS's expression DAG. pub result: Operand, } /// A statement in a LHS or RHS. #[derive(Clone, Debug)] pub enum Statement { /// An assignment statement. Assignment(Assignment), /// A path condition statement. Pc(Pc), /// A block path condition statement. BlockPc(BlockPc), } impl From for Statement { fn from(a: Assignment) -> Self { Statement::Assignment(a) } } impl From for Statement { fn from(pc: Pc) -> Self { Statement::Pc(pc) } } impl From for Statement { fn from(bpc: BlockPc) -> Self { Statement::BlockPc(bpc) } } /// An assignment, defining a value. #[derive(Clone, Debug)] pub struct Assignment { /// The name of the value being defined by this assignment. pub name: String, /// The ascribed type, if any. pub r#type: Option, /// The value being bound in this assignment. pub value: AssignmentRhs, /// Attributes describing data-flow facts known about this value. pub attributes: Vec, } /// Any value that can be assigned to a name. #[derive(Clone, Debug)] pub enum AssignmentRhs { /// An input variable. Var, /// A basic block. Block(Block), /// A phi node. Phi(Phi), /// A hole reserved for an as-of-yet-unknown instruction. ReservedInst, /// A hole reserved for an as-of-yet-unknown constant. ReservedConst, /// An instruction and its operands. Instruction(Instruction), } impl From for AssignmentRhs { fn from(b: Block) -> Self { AssignmentRhs::Block(b) } } impl From for AssignmentRhs { fn from(p: Phi) -> Self { AssignmentRhs::Phi(p) } } impl From for AssignmentRhs { fn from(i: Instruction) -> Self { AssignmentRhs::Instruction(i) } } /// An input variable. #[derive(Clone, Debug)] pub struct Var { /// Attributes describing dataflow facts known about this input variable. pub attributes: Vec, } /// A basic block. #[derive(Clone, Debug)] pub struct Block { /// The number of incoming predecessors that this basic block has in the /// control-flow graph. pub predecessors: u32, } /// A phi node. /// /// If a phi's `block` has `n` predecessors, then the length of `values` must be /// `n`. /// /// All phi nodes associated with a particular `block` will have their `i`th /// value selected when control flow comes from `block`'s `i`th predecessor. For /// example, given: /// /// ```text /// %bb = block 3 /// %a = phi %bb, 1, 2, 3 /// %b = phi %bb, 4, 5, 6 /// %c = phi %bb, 7, 8, 9 /// ``` /// /// If the basic block `%bb` has three control-flow predecessors. If it is /// entered via its first predecessor, then `%a == 1`, `%b == 4`, and `%c == /// 7`. If it is entered via its second predecessor, then `%a == 2`, `%b == 5`, /// and `%c == 8`. Finally, if it is entered via its third predecessor, then `%a /// == 3`, `%b == 6`, and `%c == 9`. #[derive(Clone, Debug)] pub struct Phi { /// The basic block that this phi node is contained within. pub block: BlockId, /// The potential values for this phi node. pub values: Vec, } macro_rules! define_instructions { ( $( $( #[$attr:meta] )* $token:literal => $inst:ident $( ( $($operand:ident),* ) )? ; )* ) => { /// A Souper instruction. #[derive(Copy, Clone, Debug)] pub enum Instruction { $( $( #[$attr] )* $inst $( { $( #[allow(missing_docs)] $operand: Operand ),* } )? , )* } #[cfg(feature = "parse")] impl crate::parse::Peek for Instruction { fn peek<'a>(parser: &mut crate::parse::Parser<'a>) -> crate::parse::Result { match parser.lookahead()? { Some(crate::parse::Token::Ident(ident)) => Ok( false $( || ident == $token )* ), _ => Ok(false), } } } #[cfg(feature = "parse")] impl crate::parse::Parse for Instruction { fn parse<'a>(parser: &mut crate::parse::Parser<'a>) -> crate::parse::Result { let ident = parser.token()?; match ident { $( #[allow(unused_assignments)] crate::parse::Token::Ident($token) => { $( let mut first = true; $( if !first { parser.comma()?; } let $operand = Operand::parse(parser)?; first = false; )* )? Ok(Instruction::$inst $( { $( $operand ),* } )? ) } )* _ => parser.error("expected instruction"), } } } #[cfg(feature = "stringify")] impl Instruction { pub(crate) fn value_ids(&self, mut f: impl FnMut(ValueId)) { match self { $( Instruction::$inst $( { $( $operand ),* } )? => { $( $( if let Operand::Value(v) = $operand { f(*v); } )* )? } )* } } pub(crate) fn operands(&self, mut f: impl FnMut(Operand)) { match self { $( Instruction::$inst $( { $( $operand ),* } )? => { $( $( f(*$operand); )* )? } )* } } pub(crate) fn instruction_name(&self) -> &'static str { match self { $( Instruction::$inst $( { $( $operand: _),* } )? => $token, )* } } } }; } define_instructions! { /// Wrapping integer addition. "add" => Add(a, b); /// Integer addition where signed overflow is undefined behavior. "addnsw" => AddNsw(a, b); /// Integer addition where unsigned overflow is undefined behavior. "addnuw" => AddNuw(a, b); /// Integer addition where any kind of overflow is undefined behavior. "addnw" => AddNw(a, b); /// Wrapping integer subtraction. "sub" => Sub(a, b); /// Integer subtraction where signed overflow is undefined behavior. "subnsw" => SubNsw(a, b); /// Integer subtraction where unsigned overflow is undefined behavior. "subnuw" => SubNuw(a, b); /// Integer subtraction where any kind of overflow is undefined behavior. "subnw" => SubNw(a, b); /// Wrapping integer multiplication. "mul" => Mul(a, b); /// Integer multiplication where signed overflow is undefined behavior. "mulnsw" => MulNsw(a, b); /// Integer multiplication where unsigned overflow is undefined behavior. "mulnuw" => MulNuw(a, b); /// Integer multiplication where any kind of overflow is undefined behavior. "mulnw" => MulNw(a, b); /// Unsigned integer division. "udiv" => Udiv(a, b); /// Signed integer division. "sdiv" => Sdiv(a, b); /// Unsigned division where `a` must be exactly divisible by `b`. If `a` is /// not exactly divisible by `b`, then the result is undefined behavior. "udivexact" => UdivExact(a, b); /// Signed division where `a` must be exactly divisible by `b`. If `a` is /// not exactly divisible by `b`, then the result is undefined behavior. "sdivexact" => SdivExact(a, b); /// Unsigned integer remainder. "urem" => Urem(a, b); /// Signed integer remainder. "srem" => Srem(a, b); /// Bit-wise and. "and" => And(a, b); /// Bit-wise or. "or" => Or(a, b); /// Bit-wise xor. "xor" => Xor(a, b); /// Bit shift left. Undefined behavior if `b` is greater than or equal to `bitwidth(a)`. "shl" => Shl(a, b); /// Bit shift left where shifting out any non-sign bits is undefined /// behavior. "shlnsw" => ShlNsw(a, b); /// Bit shift left where shifting out any non-zero bits is undefined /// behavior. "shlnuw" => ShlNuw(a, b); /// Bit shift left where shifting out any non-zero or non-sign bits is /// undefined behavior. "shlnw" => ShlNw(a, b); /// Logical bit shift right (fills left `b` bits with zero). Undefined /// behavior if `b` is greater than or equal to `bitwidth(a)`. "lshr" => Lshr(a, b); /// Logical bit shift right (fills left `b` bits with zero) where it is /// undefined behavior if any bits shifted out are non-zero. "lshrexact" => LshrExact(a, b); /// Arithmetic bit shift right (sign extends the left `b` bits). "ashr" => Ashr(a, b); /// Arithmetic bit shift right (fills left `b` bits with zero) where it is /// undefined behavior if any bits shifted out are non-zero. "ashrexact" => AshrExact(a, b); /// If `a` is 1, then evaluates to `b`, otherwise evaluates to `c`. "select" => Select(a, b, c); /// Zero extend `a`. "zext" => Zext(a); /// Sign extend `a`. "sext" => Sext(a); /// Truncate `a`. "trunc" => Trunc(a); /// `a == b`. "eq" => Eq(a, b); /// `a != b` "ne" => Ne(a, b); /// Unsigned less than. "ult" => Ult(a, b); /// Signed less than. "slt" => Slt(a, b); /// Unsigned less than or equal. "ule" => Ule(a, b); /// Signed less than or equal. "sle" => Sle(a, b); /// Count the number of 1 bits in `a`. "ctpop" => Ctpop(a); /// Swap bytes in `a`, e.g. `0xaabbccdd` becomes `0xddccbbaa`. "bswap" => Bswap(a); /// Reverse the bits in `a`. "bitreverse" => BitReverse(a); /// Count trailing zero bits in `a`. "cttz" => Cttz(a); /// Count leading one bits in `a`. "ctlz" => Ctlz(a); /// Left funnel shift. "fshl" => Fshl(a, b, c); /// Right funnel shift. "fshr" => Fshr(a, b, c); /// Wrapping signed integer addition of `a` and `b` where the result is /// extended by one bit which indicates whether the addition overflowed. "sadd.with.overflow" => SaddWithOverflow(a, b); /// Wrapping unsigned integer addition of `a` and `b` where the result is /// extended by one bit which indicates whether the addition overflowed. "uadd.with.overflow" => UaddWithOverflow(a, b); /// Wrapping signed integer subtraction of `a` and `b` where the result is /// extended by one bit which indicates whether the subtraction overflowed. "ssub.with.overflow" => SsubWithOverflow(a, b); /// Wrapping unsigned integer subtraction of `a` and `b` where the result is /// extended by one bit which indicates whether the subtraction overflowed. "usub.with.overflow" => UsubWithOverflow(a, b); /// Wrapping signed integer multiplication of `a` and `b` where the result /// is extended by one bit which indicates whether the multiplication /// overflowed. "smul.with.overflow" => SmulWithOverflow(a, b); /// Wrapping unsigned integer multiplication of `a` and `b` where the result /// is extended by one bit which indicates whether the multiplication /// overflowed. "umul.with.overflow" => UmulWithOverflow(a, b); /// Signed saturating integer addition. "sadd.sat" => SaddSat(a, b); /// Unsigned saturating integer addition. "uadd.sat" => UaddSat(a, b); /// Signed saturating integer subtraction. "ssub.sat" => SsubSat(a, b); /// Unsigned saturating integer subtraction. "usub.sat" => UsubSat(a, b); /// Extract the `b`th value from `a`. "extractvalue" => ExtractValue(a, b); /// A hole reserved for an unknown instruction or value. "hole" => Hole; /// If `a` is a poison or undef value, returns an arbitrary but fixed /// value. Otherwise returns `a`. "freeze" => Freeze(a); } /// An operand for an instruction or some other kind of operation. #[derive(Copy, Clone, Debug)] pub enum Operand { /// The id of a value defined in an earlier statement. Value(ValueId), /// A literal constant value. Constant(Constant), } impl From for Operand { fn from(c: Constant) -> Self { Operand::Constant(c) } } impl From for Operand { fn from(v: ValueId) -> Self { Operand::Value(v) } } /// Attributes describing data-flow facts known about the root of a left- or /// right-hand side. #[derive(Clone, Debug)] pub enum RootAttribute { /// Which bits of the resulting value are actually used. /// /// The vector must have a boolean for each bit in the result type, e.g. if /// the result type is `i32`, then the vector's length must be 32. /// /// If the `n`th entry in the vector is `true`, then the `n`th bit of the /// result is used. If it is `false`, then that bit is not used. DemandedBits(Vec), /// TODO HarvestedFromUse, } /// Attributes describing data-flow facts known about a particular value or /// result of an instruction. #[derive(Clone, Debug)] pub enum Attribute { /// Bits that are known to be set or unset. /// /// The vector must have an entry for each bit in the value, e.g. if the /// value's type is `i32`, then the vector's length must be 32. /// /// If the `i`th bit is known to be set, then the `i`th entry should be /// `Some(true)`. If the `i`th bit is known to be unset, then the `i`th /// entry should be `Some(false)`. If it is unknown whether the `i`th bit is /// set or unset, or it can sometimes be either, then the `i`th entry should /// be `None`. KnownBits(Vec>), /// The value is known to be a power of two. PowerOfTwo, /// The value is known to be negative. Negative, /// The value is known to be non-negative. NonNegative, /// The value is known to be non-zero. NonZero, /// The value is used by other expressions, not just this replacement's /// expression DAG. HasExternalUses, /// It is known that there are `n` sign bits in this value. SignBits(u8), /// This value is within the range `.0` (inclusive) to `.1` (exclusive). Range(i128, i128), } /// A path condition. /// /// Expresses the fact that `x` must equal `y` for the replacement to be valid. #[derive(Clone, Debug)] pub struct Pc { /// A value that must be equal to `y` for the replacement to be valid. pub x: Operand, /// A value that must be equal to `x` for the replacement to be valid. pub y: Operand, } /// A block path condition. /// /// Expresses that `x` is equal to `y` on an incoming predecessor to `block` in /// the control-flow graph. #[derive(Clone, Debug)] pub struct BlockPc { /// The basic block in question. pub block: BlockId, /// The `i`th control-flow predecessor of `block`. /// /// Zero-indexed: must be less than `block`'s total number of predecessors. pub predecessor: u32, /// Must be equal to `y` if control-flow entered `block` via `predecessor`. pub x: Operand, /// Must be equal to `x` if control-flow entered `block` via `predecessor`. pub y: Operand, } /// A constant value. /// /// Optionally has an ascribed type. #[derive(Copy, Clone, Debug)] pub struct Constant { /// The constant value. pub value: i128, /// The ascribed type, if any. pub r#type: Option, } /// A type. /// /// All Souper types are integers, they just have different bit widths. #[derive(Copy, Clone, Debug)] pub struct Type { /// The bit width of this integer type. pub width: u16, } souper-ir-2.1.0/src/lib.rs000077500000000000000000000061471374236130100153550ustar00rootroot00000000000000//! A library for manipulating [Souper] IR. //! //! [![](https://docs.rs/souper-ir/badge.svg)](https://docs.rs/souper-ir/) //! [![](https://img.shields.io/crates/v/souper-ir.svg)](https://crates.io/crates/souper-ir) //! [![](https://img.shields.io/crates/d/souper-ir.svg)](https://crates.io/crates/souper-ir) //! ![CI](https://github.com/fitzgen/souper-ir/workflows/CI/badge.svg) //! //! This crate provides AST types for parsing or generating Souper IR. It is a //! suitable building block either for writing a custom LHS extractor, or for //! translating learned optimizations into your own peephole optimizations pass. //! //! ## AST //! //! The AST type definitions and builders live in the `souper_ir::ast` module. //! //! ## Parsing Souper IR //! //! When the `parse` Cargo feature is enabled, the `souper_ir::parse` module //! contains functions for parsing Souper IR from a file or an in-memory string. //! //! ``` //! # #[cfg(feature = "parse")] //! # fn foo() -> souper_ir::parse::Result<()> { //! use std::path::Path; //! //! // We provide a filename to get better error messages. //! let filename = Path::new("example.souper"); //! //! let replacements = souper_ir::parse::parse_replacements_str(" //! ;; x + x --> 2 * x //! %0 = var //! %1 = add %0, %0 //! %2 = mul %0, 2 //! cand %1, %2 //! //! ;; x & x --> x //! %0 = var //! %1 = and %0, %0 //! cand %1, %0 //! ", Some(filename))?; //! # let _ = replacements; //! # Ok(()) //! # } //! ``` //! //! ## Emitting Souper IR's Text Format //! //! When the `stringify` Cargo feature is enabled, then the //! `souper_ir::ast::Replacement`, `souper_ir::ast::LeftHandSide`, and //! `souper_ir::ast::RightHandSide` types all implement `std::fmt::Display`. The //! `Display` implementation writes the AST type out as Souper's text format. //! //! ``` //! # #[cfg(feature = "stringify")] //! # fn foo() -> std::io::Result<()> { //! use souper_ir::ast; //! //! // Build this Souper left-hand side: //! // //! // %x:i32 = var //! // %y = mul %x, 2 //! // infer %y //! // //! // We expect that Souper would be able to synthesize a right-hand side that //! // does a left shift by one instead of a multiplication. //! //! let mut lhs = ast::LeftHandSideBuilder::default(); //! //! let x = lhs.assignment( //! Some("x".into()), //! Some(ast::Type { width: 32 }), //! ast::AssignmentRhs::Var, //! vec![], //! ); //! //! let y = lhs.assignment( //! Some("y".into()), //! None, //! ast::Instruction::Mul { //! a: x.into(), //! b: ast::Constant { value: 2, r#type: None }.into(), //! }, //! vec![], //! ); //! //! let lhs = lhs.finish(y, vec![]); //! //! // Now we can stringify the LHS (and then, presumably, give it to Souper) //! // with `std::fmt::Display`: //! //! use std::io::Write; //! //! let mut file = std::fs::File::create("my-lhs.souper")?; //! write!(&mut file, "{}", lhs)?; //! # Ok(()) //! # } //! ``` //! [Souper]: https://github.com/google/souper #![deny(missing_debug_implementations)] #![deny(missing_docs)] pub mod ast; #[cfg(feature = "parse")] pub mod parse; #[cfg(feature = "stringify")] mod stringify; souper-ir-2.1.0/src/parse.rs000066400000000000000000001157411374236130100157170ustar00rootroot00000000000000//! Parsing the Souper IR text format. //! //! This module provides both high- and low-level parsing utilities: //! //! * The high-level parsing functions are `parse_*_file` and `parse_*_string` //! which can be used to parse full replacements, LHSes, or RHSes either from //! files on disk or from an in-memory string. //! //! * The low-level parsing utilities are [the `Parse` //! trait][crate::parse::Parse] and [the `Parser` //! struct][crate::parse::Parser]. These can be used to parse a single //! instance of an LHS or RHS, for example. //! //! ## Example //! //! ``` //! # fn main() -> souper_ir::parse::Result<()> { //! use souper_ir::parse::parse_replacements_str; //! use std::path::Path; //! //! let replacements = parse_replacements_str(" //! ;; First replacement. //! %x:i32 = var //! %2lx = slt 2, %x //! pc %2lx 1 //! %1lx = slt 1, %x //! cand %1lx 1 //! //! ;; Second replacement. //! %bb = block 3 //! %phi1 = phi %bb, 1:i32, 2, 3 //! %phi2 = phi %bb, 2:i32, 4, 6 //! %phi1x2 = mul %phi1, 2 //! cand %phi2 %phi1x2 //! ", Some(Path::new("example.souper")))?; //! //! assert_eq!(replacements.len(), 2); //! # Ok(()) //! # } //! ``` use crate::ast; use id_arena::Arena; use std::{ borrow::Cow, collections::HashMap, convert::TryInto, iter::Peekable, mem, path::{Path, PathBuf}, str::CharIndices, str::FromStr, }; /* Here is the grammar, as far as I can tell. See https://github.com/google/souper/issues/782. ``` ::= | ::= * ::= 'infer' * ::= * ::= 'result' ::= | * ::= 'cand' * ::= | | ::= '=' ::= (':' )? ::= 'var' * | 'block' | 'phi' (',' )* | 'reservedinst' * | 'reservedconst' * | * ::= 'add' ',' | 'addnsw' ',' | 'addnuw' ',' | 'addnw' ',' | 'sub' ',' | 'subnsw' ',' | 'subnuw' ',' | 'subnw' ',' | 'mul' ',' | 'mulnsw' ',' | 'mulnuw' ',' | 'mulnw' ',' | 'udiv' ',' | 'sdiv' ',' | 'udivexact' ',' | 'sdivexact' ',' | 'urem' ',' | 'srem' ',' | 'and' ',' | 'or' ',' | 'xor' ',' | 'shl' ',' | 'shlnsw' ',' | 'shlnuw' ',' | 'shlnw' ',' | 'lshr' ',' | 'lshrexact' ',' | 'ashr' ',' | 'ashrexact' ',' | 'select' ',' ',' | 'zext' | 'sext' | 'trunc' | 'eq' ',' | 'ne' ',' | 'ult' ',' | 'slt' ',' | 'ule' ',' | 'sle' ',' | 'ctpop' | 'bswap' | 'bitreverse' | 'cttz' | 'ctlz' | 'fshl' ',' ',' | 'fshr' ',' ',' | 'sadd.with.overflow' ',' | 'uadd.with.overflow' ',' | 'ssub.with.overflow' ',' | 'usub.with.overflow' ',' | 'smul.with.overflow' ',' | 'umul.with.overflow' ',' | 'sadd.sat' ',' | 'uadd.sat' ',' | 'ssub.sat' ',' | 'usub.sat' ',' | 'extractvalue' ',' | 'hole' | 'freeze' ::= | ::= '(' ')' ::= 'demandedBits' '=' regexp([0|1]+) | 'harvestedFromUse' ::= '(' ')' ::= 'knownBits' '=' regexp([0|1|x]+) | 'powerOfTwo' | 'negative' | 'nonNegative' | 'nonZero' | 'hasExternalUses' | 'signBits' '=' | 'range' '=' '[' ',' ')' ::= 'pc' ::= 'blockpc' ::= (':' )? ::= regexp(-?\d+) ::= regexp(i\d+) ::= regexp(%[a-zA-Z0-9_]+) ::= regexp([a-zA-Z][a-zA-Z0-9_]+) ``` */ /// An error that occurs during parsing. pub struct ParseError { inner: Box, } impl std::fmt::Debug for ParseError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self) } } #[derive(Debug)] struct ParseErrorInner { kind: ParseErrorKind, pos: Option<(usize, usize, String)>, filepath: Option, } #[derive(Debug)] enum ParseErrorKind { Io(std::io::Error), Parse(usize, String), } impl std::fmt::Display for ParseError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match &self.inner.kind { ParseErrorKind::Io(io) => write!(f, "IO error: {}", io), ParseErrorKind::Parse(offset, msg) => { let (line, column, line_text) = if let Some((l, c, t)) = &self.inner.pos { (*l, *c, Cow::Borrowed(t)) } else { (0, 0, Cow::Owned(format!("byte offset {}", offset))) }; write!( f, "\ {file}:{line}:{column}: {msg} | {line:4} | {line_text} | {marker:>column$}", file = self .inner .filepath .as_ref() .map_or(Path::new("unknown"), |p| &*p) .display(), line = line + 1, column = column + 1, line_text = line_text, msg = msg, marker = "^" ) } } } } impl std::error::Error for ParseError {} impl From for ParseError { fn from(e: std::io::Error) -> Self { ParseError { inner: Box::new(ParseErrorInner { kind: ParseErrorKind::Io(e), pos: None, filepath: None, }), } } } impl ParseError { pub(crate) fn new(offset: usize, msg: impl Into) -> Self { ParseError { inner: Box::new(ParseErrorInner { kind: ParseErrorKind::Parse(offset, msg.into()), pos: None, filepath: None, }), } } fn offset(&self) -> Option { if let ParseErrorKind::Parse(offset, _) = self.inner.kind { Some(offset) } else { None } } fn line_col_in(source: &str, offset: usize) -> Option<(usize, usize)> { let mut current_offset = 0; for (line_num, line) in source.split_terminator('\n').enumerate() { // +1 for the `\n`. current_offset += line.len() + 1; if current_offset >= offset { return Some((line_num, line.len().saturating_sub(current_offset - offset))); } } None } pub(crate) fn set_source(&mut self, source: &str) { if let Some(offset) = self.offset() { if let Some((line, col)) = Self::line_col_in(source, offset) { let line_text = source.lines().nth(line).unwrap_or("").to_string(); self.inner.pos = Some((line, col, line_text)); } } } pub(crate) fn set_filepath(&mut self, filepath: impl Into) { self.inner.filepath = Some(filepath.into()); } } /// A `Result` type for parsing. /// /// Either `Ok(T)` or `Err(ParseError)`. pub type Result = std::result::Result; /// Keep parsing `P`s until we reach EOF. fn parse_until_eof

(parser: &mut Parser) -> Result> where P: Parse, { let mut ps = vec![]; while !parser.eof()? { ps.push(P::parse(parser)?); } Ok(ps) } /// Execute the given function and if it returns a parse error, set the source /// and filename on the error. fn with_source_and_file( source: &str, file: Option<&Path>, f: impl FnOnce() -> Result, ) -> Result { f().map_err(|mut e| { e.set_source(source); if let Some(f) = file { e.set_filepath(f); } e }) } /// Parse a sequence of [`Replacement`s][crate::ast::Replacement] from an /// in-memory string. pub fn parse_replacements_str( source: &str, filename: Option<&Path>, ) -> Result> { with_source_and_file(source, filename, || { let mut parser = Parser::new(source); parse_until_eof(&mut parser) }) } /// Parse a sequence of [`Replacement`s][crate::ast::Replacement] from a file on /// disk. pub fn parse_replacements_file(path: &Path) -> Result> { let source = std::fs::read_to_string(path)?; parse_replacements_str(&source, Some(path)) } /// Parse a sequence of [`LeftHandSide`s][crate::ast::LeftHandSide] from an /// in-memory string. pub fn parse_left_hand_sides_str( source: &str, filename: Option<&Path>, ) -> Result> { with_source_and_file(source, filename, || { let mut parser = Parser::new(source); parse_until_eof(&mut parser) }) } /// Parse a sequence of [`LeftHandSide`s][crate::ast::LeftHandSide] from a file on /// disk. pub fn parse_left_hand_sides_file(path: &Path) -> Result> { let source = std::fs::read_to_string(path)?; parse_left_hand_sides_str(&source, Some(path)) } /// Parse a sequence of [`RightHandSide`s][crate::ast::RightHandSide] from an /// in-memory string. pub fn parse_right_hand_sides_str( source: &str, filename: Option<&Path>, ) -> Result> { with_source_and_file(source, filename, || { let mut parser = Parser::new(source); parse_until_eof(&mut parser) }) } /// Parse a sequence of [`RightHandSide`s][crate::ast::RightHandSide] from a file on /// disk. pub fn parse_right_hand_sides_file(path: &Path) -> Result> { let source = std::fs::read_to_string(path)?; parse_right_hand_sides_str(&source, Some(path)) } #[derive(Debug)] struct Lexer<'a> { source: &'a str, chars: Peekable>, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum Token<'a> { Ident(&'a str), ValName(&'a str), KnownBits(&'a str), Int(&'a str), Comma, Colon, Eq, OpenParen, CloseParen, OpenBracket, } fn is_ident_char(c: char) -> bool { match c { '0'..='9' | 'a'..='z' | 'A'..='Z' | '_' | '.' => true, _ => false, } } impl<'a> Lexer<'a> { fn new(source: &'a str) -> Self { Lexer { source, chars: source.char_indices().peekable(), } } fn expect_char(&mut self, c: char) -> Result<()> { match self.chars.next() { Some((_, c2)) if c2 == c => Ok(()), Some((offset, c2)) => Err(ParseError::new( offset, format!("expected '{}', found '{}'", c, c2), )), None => Err(ParseError::new( self.source.len().saturating_sub(1), "unexpected EOF", )), } } /// Get the next token. /// /// Returns `None` at EOF. fn next_token(&mut self) -> Result)>> { loop { match self.chars.peek() { // EOF. None => return Ok(None), // Eat whitespace. Some((_, c)) if c.is_whitespace() => { while self.chars.peek().map_or(false, |(_, c)| c.is_whitespace()) { self.chars.next().unwrap(); } } // Eat comments. Some((_, ';')) => { while self.chars.peek().map_or(false, |(_, c)| *c != '\n') { self.chars.next().unwrap(); } } _ => break, } } match *self.chars.peek().unwrap() { (start, ',') => { self.chars.next().unwrap(); Ok(Some((start, Token::Comma))) } (start, '=') => { self.chars.next().unwrap(); Ok(Some((start, Token::Eq))) } (start, ':') => { self.chars.next().unwrap(); Ok(Some((start, Token::Colon))) } (start, '(') => { self.chars.next().unwrap(); Ok(Some((start, Token::OpenParen))) } (start, ')') => { self.chars.next().unwrap(); Ok(Some((start, Token::CloseParen))) } (start, '[') => { self.chars.next().unwrap(); Ok(Some((start, Token::OpenBracket))) } (start, '%') => { self.chars.next().unwrap(); let mut end = start + 1; while self.chars.peek().map_or(false, |&(_, c)| is_ident_char(c)) { let (i, _) = self.chars.next().unwrap(); end = i + 1; } if start + 1 == end { Err(ParseError::new(start, "expected value name")) } else { Ok(Some((start, Token::ValName(&self.source[start..end])))) } } (start, c) if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') => { self.chars.next().unwrap(); let mut end = start + 1; while self.chars.peek().map_or(false, |&(_, c)| is_ident_char(c)) { let (i, _) = self.chars.next().unwrap(); end = i + 1; } let ident = &self.source[start..end]; if ident != "knownBits" { return Ok(Some((start, Token::Ident(ident)))); } self.expect_char('=')?; let bit_pattern_start = start + "knownBits=".len(); let mut bit_pattern_end = bit_pattern_start; while self .chars .peek() .map_or(false, |&(_, c)| c == '0' || c == '1' || c == 'x') { let (i, _) = self.chars.next().unwrap(); bit_pattern_end = i + 1; } if bit_pattern_start == bit_pattern_end { Err(ParseError::new( bit_pattern_start, "expected [0|1|x]+ bit pattern for knownBits", )) } else { Ok(Some(( start, Token::KnownBits(&self.source[bit_pattern_start..bit_pattern_end]), ))) } } (start, c) if c == '-' || ('0' <= c && c <= '9') => { self.chars.next().unwrap(); let mut end = start + 1; while self .chars .peek() .map_or(false, |&(_, c)| '0' <= c && c <= '9') { let (i, _) = self.chars.next().unwrap(); end = i + 1; } Ok(Some((start, Token::Int(&self.source[start..end])))) } (start, c) => Err(ParseError::new(start, format!("unexpected '{}'", c))), } } } /// A Souper text parser buffer. /// /// Manages lexing and lookahead, as well as some parsed values and binding /// scopes. #[derive(Debug)] pub struct Parser<'a> { lexer: Lexer<'a>, /// Lookahead token that we've peeked at, if any. lookahead: Option>, /// Our current offset into the string we are parsing. offset: usize, /// Statements being built up during parsing. statements: Arena, /// Scope mapping value names to their value id. values: HashMap, /// Scope mapping block names to their block id. blocks: HashMap, } impl<'a> Parser<'a> { /// Construct a new parser for the given Souper source text. pub fn new(source: &'a str) -> Self { Parser { lexer: Lexer::new(source), lookahead: None, offset: 0, statements: Arena::new(), values: HashMap::new(), blocks: HashMap::new(), } } pub(crate) fn lookahead(&mut self) -> Result>> { if self.lookahead.is_none() { if let Some((offset, token)) = self.lexer.next_token()? { self.offset = offset; self.lookahead = Some(token); } } Ok(self.lookahead) } pub(crate) fn lookahead_ident(&mut self, ident: &str) -> Result { Ok(self.lookahead()? == Some(Token::Ident(ident))) } /// Are we at EOF? pub(crate) fn eof(&mut self) -> Result { Ok(self.lookahead()?.is_none()) } pub(crate) fn error(&self, msg: impl Into) -> Result { Err(ParseError::new(self.offset, msg)) } pub(crate) fn token(&mut self) -> Result> { if let Some(tok) = self.lookahead.take() { Ok(tok) } else { match self.lexer.next_token()? { Some((offset, tok)) => { self.offset = offset; Ok(tok) } None => { self.offset = self.lexer.source.len().saturating_sub(1); self.error("unexpected EOF") } } } } pub(crate) fn val_name(&mut self) -> Result<&'a str> { match self.token()? { Token::ValName(s) => Ok(s), _ => self.error("expected a value name"), } } pub(crate) fn ident(&mut self, which: &str) -> Result<()> { match self.token()? { Token::Ident(s) if s == which => Ok(()), _ => self.error(format!("expected '{}'", which)), } } pub(crate) fn int(&mut self) -> Result { match self.token()? { Token::Int(x) => match i128::from_str(x) { Ok(x) => Ok(x), Err(e) => self.error(e.to_string()), }, _ => self.error("expected an integer literal"), } } pub(crate) fn eq(&mut self) -> Result<()> { match self.token()? { Token::Eq => Ok(()), _ => self.error("expected '='"), } } pub(crate) fn colon(&mut self) -> Result<()> { match self.token()? { Token::Colon => Ok(()), _ => self.error("expected ':'"), } } pub(crate) fn comma(&mut self) -> Result<()> { match self.token()? { Token::Comma => Ok(()), _ => self.error("expected ','"), } } pub(crate) fn open_paren(&mut self) -> Result<()> { match self.token()? { Token::OpenParen => Ok(()), _ => self.error("expected '('"), } } pub(crate) fn close_paren(&mut self) -> Result<()> { match self.token()? { Token::CloseParen => Ok(()), _ => self.error("expected ')'"), } } pub(crate) fn open_bracket(&mut self) -> Result<()> { match self.token()? { Token::OpenBracket => Ok(()), _ => self.error("expected '['"), } } fn take_statements(&mut self) -> Arena { self.values.clear(); self.blocks.clear(); mem::replace(&mut self.statements, Arena::new()) } } /// A trait for AST nodes that can be parsed from text. pub trait Parse: Sized { /// Parse a `Self` from the given buffer. fn parse<'a>(parser: &mut Parser<'a>) -> Result; } /// A trait for whether an AST node looks like it comes next. pub trait Peek { /// Does it look like we can parse a `Self` from the given buffer? fn peek<'a>(parser: &mut Parser<'a>) -> Result; } impl

Parse for Option

where P: Peek + Parse, { fn parse<'a>(parser: &mut Parser<'a>) -> Result { if P::peek(parser)? { Ok(Some(P::parse(parser)?)) } else { Ok(None) } } } impl

Parse for Vec

where P: Peek + Parse, { fn parse<'a>(parser: &mut Parser<'a>) -> Result { let mut ps = vec![]; while P::peek(parser)? { ps.push(P::parse(parser)?); } Ok(ps) } } impl Parse for ast::Replacement { fn parse<'a>(parser: &mut Parser<'a>) -> Result { while ast::Statement::peek(parser)? { parse_statement(parser)?; } if ast::Infer::peek(parser)? { let lhs = ast::Infer::parse(parser)?; while ast::Statement::peek(parser)? { parse_statement(parser)?; } parser.ident("result")?; let rhs = ast::Operand::parse(parser)?; let statements = parser.take_statements(); return Ok(ast::Replacement::LhsRhs { statements, lhs, rhs, }); } let cand = ast::Cand::parse(parser)?; let statements = parser.take_statements(); Ok(ast::Replacement::Cand { statements, cand }) } } impl Parse for ast::LeftHandSide { fn parse<'a>(parser: &mut Parser<'a>) -> Result { while ast::Statement::peek(parser)? { parse_statement(parser)?; } let infer = ast::Infer::parse(parser)?; let statements = parser.take_statements(); Ok(ast::LeftHandSide { statements, infer }) } } impl Parse for ast::RightHandSide { fn parse<'a>(parser: &mut Parser<'a>) -> Result { while ast::Statement::peek(parser)? { parse_statement(parser)?; } parser.ident("result")?; let result = ast::Operand::parse(parser)?; let statements = parser.take_statements(); Ok(ast::RightHandSide { statements, result }) } } impl Peek for ast::Statement { fn peek<'a>(parser: &mut Parser<'a>) -> Result { Ok(ast::Assignment::peek(parser)? || ast::Pc::peek(parser)? || ast::BlockPc::peek(parser)?) } } fn parse_statement(parser: &mut Parser) -> Result<()> { if ast::Assignment::peek(parser)? { let assignment = ast::Assignment::parse(parser)?; let name = assignment.name.clone(); let is_block = matches!(assignment.value, ast::AssignmentRhs::Block(_)); let id = parser .statements .alloc(ast::Statement::Assignment(assignment)); parser.values.insert(name.clone(), ast::ValueId(id)); if is_block { parser.blocks.insert(name, ast::BlockId(ast::ValueId(id))); } return Ok(()); } if ast::Pc::peek(parser)? { let pc = ast::Pc::parse(parser)?; parser.statements.alloc(ast::Statement::Pc(pc)); return Ok(()); } if ast::BlockPc::peek(parser)? { let blockpc = ast::BlockPc::parse(parser)?; parser.statements.alloc(ast::Statement::BlockPc(blockpc)); return Ok(()); } parser.error("expected either an assignment, a pc statement, of a blockpc statement") } impl Peek for ast::Infer { fn peek<'a>(parser: &mut Parser<'a>) -> Result { parser.lookahead_ident("infer") } } impl Parse for ast::Infer { fn parse<'a>(parser: &mut Parser<'a>) -> Result { parser.ident("infer")?; let name = parser.val_name()?; let value = parser.values.get(name).copied().ok_or_else(|| { ParseError::new(parser.offset, format!("no value named '{}' in scope", name)) })?; let attributes = Vec::::parse(parser)?; Ok(ast::Infer { value, attributes }) } } impl Parse for ast::Cand { fn parse<'a>(parser: &mut Parser<'a>) -> Result { parser.ident("cand")?; let lhs = ast::Operand::parse(parser)?; let rhs = ast::Operand::parse(parser)?; let attributes = Vec::::parse(parser)?; Ok(ast::Cand { lhs, rhs, attributes, }) } } impl Peek for ast::RootAttribute { fn peek<'a>(parser: &mut Parser<'a>) -> Result { Ok(parser.lookahead()? == Some(Token::OpenParen)) } } impl Parse for ast::RootAttribute { fn parse<'a>(parser: &mut Parser<'a>) -> Result { parser.open_paren()?; match parser.token()? { Token::Ident("harvestedFromUse") => { parser.close_paren()?; Ok(ast::RootAttribute::HarvestedFromUse) } Token::Ident("demandedBits") => { parser.eq()?; match parser.token()? { Token::Int(i) => { let mut bits = Vec::with_capacity(i.len()); for ch in i.chars() { match ch { '0' => bits.push(false), '1' => bits.push(true), _ => return parser.error("expected [0|1]+ bit pattern"), } } parser.close_paren()?; Ok(ast::RootAttribute::DemandedBits(bits)) } _ => parser.error("expected [0|1]+ bit pattern"), } } _ => parser.error("expected 'demandedBits' or 'harvestedFromUse'"), } } } impl Peek for ast::Assignment { fn peek<'a>(parser: &mut Parser<'a>) -> Result { Ok(matches!(parser.lookahead()?, Some(Token::ValName(_)))) } } impl Parse for ast::Assignment { fn parse<'a>(parser: &mut Parser<'a>) -> Result { let name = parser.val_name()?.to_string(); if parser.values.contains_key(&name) { return parser.error(format!("cannot redefine '{}'", name)); } let r#type = if parser.lookahead()? == Some(Token::Colon) { parser.colon()?; Some(ast::Type::parse(parser)?) } else { None }; parser.eq()?; let value = ast::AssignmentRhs::parse(parser)?; let attributes = Vec::::parse(parser)?; Ok(ast::Assignment { name, r#type, value, attributes, }) } } impl Parse for ast::AssignmentRhs { fn parse<'a>(parser: &mut Parser<'a>) -> Result { if parser.lookahead_ident("var")? { parser.ident("var")?; return Ok(ast::AssignmentRhs::Var); } if ast::Block::peek(parser)? { let block = ast::Block::parse(parser)?; return Ok(ast::AssignmentRhs::Block(block)); } if ast::Phi::peek(parser)? { let phi = ast::Phi::parse(parser)?; return Ok(ast::AssignmentRhs::Phi(phi)); } if parser.lookahead_ident("reservedinst")? { parser.ident("reservedinst")?; return Ok(ast::AssignmentRhs::ReservedInst); } if parser.lookahead_ident("reservedconst")? { parser.ident("reservedconst")?; return Ok(ast::AssignmentRhs::ReservedConst); } if ast::Instruction::peek(parser)? { let inst = ast::Instruction::parse(parser)?; return Ok(ast::AssignmentRhs::Instruction(inst)); } parser.error("expected a constant, var, block, phi, or instruction") } } impl Peek for ast::Pc { fn peek<'a>(parser: &mut Parser<'a>) -> Result { parser.lookahead_ident("pc") } } impl Parse for ast::Pc { fn parse<'a>(parser: &mut Parser<'a>) -> Result { parser.ident("pc")?; let x = ast::Operand::parse(parser)?; let y = ast::Operand::parse(parser)?; Ok(ast::Pc { x, y }) } } impl Peek for ast::BlockPc { fn peek<'a>(parser: &mut Parser<'a>) -> Result { parser.lookahead_ident("blockpc") } } impl Parse for ast::BlockPc { fn parse<'a>(parser: &mut Parser<'a>) -> Result { parser.ident("blockpc")?; let name = parser.val_name()?; let block = parser.blocks.get(name).copied().ok_or_else(|| { ParseError::new(parser.offset, format!("unknown block '{}'", name)) })?; let predecessor = >::try_into(parser.int()?) .map_err(|e| ParseError::new(parser.offset, e.to_string()))?; let x = ast::Operand::parse(parser)?; let y = ast::Operand::parse(parser)?; Ok(ast::BlockPc { block, predecessor, x, y, }) } } impl Parse for ast::Type { fn parse<'a>(parser: &mut Parser<'a>) -> Result { match parser.token()? { Token::Ident(ident) if ident.starts_with('i') => match u16::from_str(&ident[1..]) { Ok(width) if width > 0 => return Ok(ast::Type { width }), _ => {} }, _ => {} } parser.error("expected a type (like 'i32', 'i8', or 'i1')") } } impl Peek for ast::Constant { fn peek<'a>(parser: &mut Parser<'a>) -> Result { Ok(matches!(parser.lookahead()?, Some(Token::Int(_)))) } } impl Parse for ast::Constant { fn parse<'a>(parser: &mut Parser<'a>) -> Result { let value = parser.int()?; let r#type = if parser.lookahead()? == Some(Token::Colon) { parser.colon()?; Some(ast::Type::parse(parser)?) } else { None }; Ok(ast::Constant { value, r#type }) } } impl Peek for ast::Block { fn peek<'a>(parser: &mut Parser<'a>) -> Result { parser.lookahead_ident("block") } } impl Parse for ast::Block { fn parse<'a>(parser: &mut Parser<'a>) -> Result { parser.ident("block")?; let predecessors: u32 = >::try_into(parser.int()?) .map_err(|e| ParseError::new(parser.offset, e.to_string()))?; Ok(ast::Block { predecessors }) } } impl Peek for ast::Phi { fn peek<'a>(parser: &mut Parser<'a>) -> Result { parser.lookahead_ident("phi") } } impl Parse for ast::Phi { fn parse<'a>(parser: &mut Parser<'a>) -> Result { parser.ident("phi")?; let name = parser.val_name()?; let block = parser.blocks.get(name).copied().ok_or_else(|| { ParseError::new(parser.offset, format!("unknown block '{}'", name)) })?; let predecessors = match parser.statements[(block.0).0] { ast::Statement::Assignment(ast::Assignment { value: ast::AssignmentRhs::Block(ast::Block { predecessors }), .. }) => predecessors, _ => unreachable!(), }; let mut values = vec![]; for _ in 0..predecessors { parser.comma()?; values.push(ast::Operand::parse(parser)?); } Ok(ast::Phi { block, values }) } } impl Peek for ast::Attribute { fn peek<'a>(parser: &mut Parser<'a>) -> Result { Ok(parser.lookahead()? == Some(Token::OpenParen)) } } impl Parse for ast::Attribute { fn parse<'a>(parser: &mut Parser<'a>) -> Result { parser.open_paren()?; match parser.token()? { Token::KnownBits(kb) => { let mut bits = Vec::with_capacity(kb.len()); for b in kb.chars() { match b { '0' => bits.push(Some(false)), '1' => bits.push(Some(true)), 'x' => bits.push(None), _ => unreachable!(), } } parser.close_paren()?; Ok(ast::Attribute::KnownBits(bits)) } Token::Ident("powerOfTwo") => { parser.close_paren()?; Ok(ast::Attribute::PowerOfTwo) } Token::Ident("negative") => { parser.close_paren()?; Ok(ast::Attribute::Negative) } Token::Ident("nonNegative") => { parser.close_paren()?; Ok(ast::Attribute::NonNegative) } Token::Ident("nonZero") => { parser.close_paren()?; Ok(ast::Attribute::NonZero) } Token::Ident("hasExternalUses") => { parser.close_paren()?; Ok(ast::Attribute::HasExternalUses) } Token::Ident("signBits") => { parser.eq()?; let bits = >::try_into(parser.int()?) .map_err(|e| ParseError::new(parser.offset, e.to_string()))?; parser.close_paren()?; Ok(ast::Attribute::SignBits(bits)) } Token::Ident("range") => { parser.eq()?; parser.open_bracket()?; let min = parser.int()?; parser.comma()?; let max = parser.int()?; parser.close_paren()?; parser.close_paren()?; Ok(ast::Attribute::Range(min, max)) } Token::Ident(id) => parser.error(format!("unknown attribute '{}'", id)), _ => parser.error("expected an attribute identifier"), } } } impl Parse for ast::Operand { fn parse<'a>(parser: &mut Parser<'a>) -> Result { if matches!(parser.lookahead()?, Some(Token::ValName(_))) { let name = parser.val_name()?; let value = parser.values.get(name).copied().ok_or_else(|| { ParseError::new(parser.offset, format!("unknown value '{}'", name)) })?; Ok(ast::Operand::Value(value)) } else { let constant = ast::Constant::parse(parser)?; Ok(ast::Operand::Constant(constant)) } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_error_display() { let source = "\ hello, how are you? > I am good, thanks thats good "; let mut err = ParseError::new(45, "missing apostrophe"); err.set_source(source); err.set_filepath("path/to/foo.txt"); let expected = "\ path/to/foo.txt:3:5: missing apostrophe | 3 | thats good | ^"; let actual = err.to_string(); assert_eq!(expected, actual); } #[test] fn test_lexer_tokens() { use super::Token::*; macro_rules! tokenizes { ( $( $source:expr => [ $($tok:expr),* $(,)* ]; )* ) => { $({ eprintln!("=== Lexing {:?} ===", $source); let mut lexer = Lexer::new($source); $( let expected = $tok; eprintln!("Expect: {:?}", expected); let actual = lexer.next_token() .expect("should not have an error during lexing") .expect("should not hit EOF") .1; eprintln!("Actual: {:?}", actual); assert_eq!(expected, actual); )* assert!(lexer.next_token().unwrap().is_none()); })* } } tokenizes! { "foo foo1 foo_foo FOO" => [ Ident("foo"), Ident("foo1"), Ident("foo_foo"), Ident("FOO"), ]; "%0 %foo %FOO %foo1" => [ ValName("%0"), ValName("%foo"), ValName("%FOO"), ValName("%foo1"), ]; "knownBits=0 knownBits=1 knownBits=x knownBits=01x01x01x" => [ KnownBits("0"), KnownBits("1"), KnownBits("x"), KnownBits("01x01x01x"), ]; ", : = ( ) [" => [Comma, Colon, Eq, OpenParen, CloseParen, OpenBracket]; "1234 -4567" => [Int("1234"), Int("-4567")]; "%0:i8" => [ValName("%0"), Colon, Ident("i8")]; "hello ; blah blah blah\n goodbye" => [Ident("hello"), Ident("goodbye")]; } } #[test] fn test_lexer_offsets() { macro_rules! offsets { ( $source:expr => [ $($offset:expr),* $(,)* ]; ) => { let mut lexer = Lexer::new($source); $( let expected = $offset; eprintln!("Expect: {:?}", expected); let actual = lexer.next_token() .expect("should not have an error during lexing") .expect("should not hit EOF") .0; eprintln!("Actual: {:?}", actual); assert_eq!(expected, actual); )* assert!(lexer.next_token().unwrap().is_none()); } } #[rustfmt::skip] offsets! { // 1 2 3 4 //12345678901234567890123456789012345678901234567890 "foo %123 knownBits=01x , : = ( ) [ 42" => [ 0, 4, 9, 23, 27, 31, 35, 39, 43, 47 ]; } } } souper-ir-2.1.0/src/stringify.rs000066400000000000000000000303741374236130100166210ustar00rootroot00000000000000//! Emitting Souper IR's text format. use crate::ast; use id_arena::{Arena, Id}; use std::{ collections::HashSet, fmt::{self, Display}, }; /// Like `std::fmt::Display`, but with an arena of `ast::Statement`s as context. trait DisplayWithContext { fn display(&self, statements: &Arena, f: &mut fmt::Formatter) -> fmt::Result; } fn topo_sort_statements( stmts: &Arena, seen: &mut HashSet>, root: Id, ) -> Vec> { let mut seq = vec![]; let mut stack = vec![Entry::Trace(root)]; while let Some(entry) = stack.pop() { let id = match entry { Entry::Finished(id) => { seq.push(id); continue; } Entry::Trace(id) if seen.contains(&id) => continue, Entry::Trace(id) => { seen.insert(id); id } }; stack.push(Entry::Finished(id)); match &stmts[id] { ast::Statement::Pc(ast::Pc { x, y }) => { if let ast::Operand::Value(v) = *y { stack.push(Entry::Trace(v.into())); } if let ast::Operand::Value(v) = *x { stack.push(Entry::Trace(v.into())); } } ast::Statement::BlockPc(ast::BlockPc { block, predecessor: _, x, y, }) => { if let ast::Operand::Value(v) = *y { stack.push(Entry::Trace(v.into())); } if let ast::Operand::Value(v) = *x { stack.push(Entry::Trace(v.into())); } stack.push(Entry::Trace((*block).into())); } ast::Statement::Assignment(ast::Assignment { value, .. }) => match value { | ast::AssignmentRhs::Var | ast::AssignmentRhs::Block(_) | ast::AssignmentRhs::ReservedInst | ast::AssignmentRhs::ReservedConst => continue, ast::AssignmentRhs::Phi(ast::Phi { block, values }) => { for v in values.iter().rev().copied() { if let ast::Operand::Value(v) = v { stack.push(Entry::Trace(v.into())); } } stack.push(Entry::Trace((*block).into())); } ast::AssignmentRhs::Instruction(inst) => { let n = stack.len(); inst.value_ids(|v| stack.push(Entry::Trace(v.into()))); stack[n..].reverse(); } }, } } return seq; enum Entry { Trace(Id), Finished(Id), } } impl Display for ast::Replacement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut seen = HashSet::new(); match self { ast::Replacement::LhsRhs { statements, lhs, rhs, } => { for s in topo_sort_statements(statements, &mut seen, lhs.value.into()) { statements[s].display(statements, f)?; } lhs.display(statements, f)?; match rhs { ast::Operand::Value(v) => { for s in topo_sort_statements(statements, &mut seen, (*v).into()) { statements[s].display(statements, f)?; } writeln!(f, "result {}", self.assignment(*v).name) } ast::Operand::Constant(ast::Constant { value, r#type }) => { write!(f, "result {}", value)?; if let Some(ty) = r#type { write!(f, ":{}", ty)?; } writeln!(f) } } } ast::Replacement::Cand { statements, cand } => { if let ast::Operand::Value(v) = cand.lhs { for s in topo_sort_statements(statements, &mut seen, v.into()) { statements[s].display(statements, f)?; } } if let ast::Operand::Value(v) = cand.rhs { for s in topo_sort_statements(statements, &mut seen, v.into()) { statements[s].display(statements, f)?; } } cand.display(statements, f) } } } } impl Display for ast::LeftHandSide { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut seen = HashSet::new(); for s in topo_sort_statements(&self.statements, &mut seen, self.infer.value.into()) { self.statements[s].display(&self.statements, f)?; } self.infer.display(&self.statements, f) } } impl Display for ast::RightHandSide { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut seen = HashSet::new(); if let ast::Operand::Value(v) = self.result { for s in topo_sort_statements(&self.statements, &mut seen, v.into()) { self.statements[s].display(&self.statements, f)?; } } write!(f, "result ")?; self.result.display(&self.statements, f) } } fn assignment(statements: &Arena, id: ast::ValueId) -> &ast::Assignment { match &statements[id.into()] { ast::Statement::Assignment(a) => a, _ => panic!("use of an `id` that is not from this `Replacement`'s arena"), } } impl DisplayWithContext for ast::Infer { fn display(&self, statements: &Arena, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "infer ")?; self.value.display(statements, f)?; for attr in &self.attributes { write!(f, " {}", attr)?; } writeln!(f) } } impl DisplayWithContext for ast::Cand { fn display(&self, statements: &Arena, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "cand ")?; self.lhs.display(statements, f)?; write!(f, " ")?; self.rhs.display(statements, f)?; for attr in &self.attributes { write!(f, " {}", attr)?; } writeln!(f) } } impl DisplayWithContext for ast::Statement { fn display(&self, statements: &Arena, f: &mut fmt::Formatter) -> fmt::Result { match self { ast::Statement::Assignment(a) => a.display(statements, f)?, ast::Statement::Pc(pc) => pc.display(statements, f)?, ast::Statement::BlockPc(bpc) => bpc.display(statements, f)?, } writeln!(f) } } impl DisplayWithContext for ast::Assignment { fn display(&self, statements: &Arena, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.name)?; if let Some(ty) = self.r#type { write!(f, ":{}", ty)?; } write!(f, " = ")?; self.value.display(statements, f)?; for attr in &self.attributes { write!(f, " {}", attr)?; } Ok(()) } } impl DisplayWithContext for ast::AssignmentRhs { fn display(&self, statements: &Arena, f: &mut fmt::Formatter) -> fmt::Result { match self { ast::AssignmentRhs::Var => write!(f, "var"), ast::AssignmentRhs::Block(block) => ::fmt(block, f), ast::AssignmentRhs::Phi(phi) => phi.display(statements, f), ast::AssignmentRhs::ReservedInst => write!(f, "reservedinst"), ast::AssignmentRhs::ReservedConst => write!(f, "reservedconst"), ast::AssignmentRhs::Instruction(inst) => inst.display(statements, f), } } } impl Display for ast::Block { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "block {}", self.predecessors) } } impl DisplayWithContext for ast::Phi { fn display(&self, statements: &Arena, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "phi ")?; self.block.display(statements, f)?; for v in &self.values { write!(f, ", ")?; v.display(statements, f)?; } Ok(()) } } impl DisplayWithContext for ast::Instruction { fn display(&self, statements: &Arena, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.instruction_name())?; let mut first = true; let mut result = Ok(()); self.operands(|x| { result = result.and_then(|_| { if first { write!(f, " ")?; first = false; } else { write!(f, ", ")?; } x.display(statements, f) }); }); result } } impl DisplayWithContext for ast::Pc { fn display(&self, statements: &Arena, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "pc ")?; self.x.display(statements, f)?; write!(f, " ")?; self.y.display(statements, f) } } impl DisplayWithContext for ast::BlockPc { fn display(&self, statements: &Arena, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "blockpc ")?; self.block.display(statements, f)?; write!(f, " {}", self.predecessor)?; self.x.display(statements, f)?; write!(f, " ")?; self.y.display(statements, f) } } impl Display for ast::RootAttribute { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "(")?; match self { ast::RootAttribute::DemandedBits(db) => { write!(f, "demandedBits=")?; for b in db { if *b { write!(f, "1")?; } else { write!(f, "0")?; } } } ast::RootAttribute::HarvestedFromUse => write!(f, "harvestedFromUse")?, } write!(f, ")") } } impl Display for ast::Attribute { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "(")?; match self { ast::Attribute::KnownBits(bits) => { write!(f, "knownBits=")?; for b in bits { match b { None => write!(f, "x")?, Some(true) => write!(f, "1")?, Some(false) => write!(f, "0")?, } } } ast::Attribute::PowerOfTwo => write!(f, "powerOfTwo")?, ast::Attribute::Negative => write!(f, "negative")?, ast::Attribute::NonNegative => write!(f, "nonNegative")?, ast::Attribute::NonZero => write!(f, "nonZero")?, ast::Attribute::HasExternalUses => write!(f, "hasExternalUses")?, ast::Attribute::SignBits(n) => write!(f, "signBits={}", n)?, ast::Attribute::Range(min, max) => write!(f, "range=[{},{})", min, max)?, } write!(f, ")") } } impl DisplayWithContext for ast::Operand { fn display(&self, statements: &Arena, f: &mut fmt::Formatter) -> fmt::Result { match self { ast::Operand::Value(id) => id.display(statements, f), ast::Operand::Constant(c) => ::fmt(c, f), } } } impl Display for ast::Constant { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.value)?; if let Some(ty) = self.r#type { write!(f, ":{}", ty)?; } Ok(()) } } impl DisplayWithContext for ast::ValueId { fn display(&self, statements: &Arena, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", assignment(statements, *self).name) } } impl DisplayWithContext for ast::BlockId { fn display(&self, statements: &Arena, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", assignment(statements, self.0).name) } } impl Display for ast::Type { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "i{}", self.width) } }