├── .gitignore ├── abi ├── src │ ├── lib.rs │ ├── align.rs │ └── size.rs └── Cargo.toml ├── mir ├── src │ ├── lib.rs │ ├── tyctxt.rs │ ├── serialize.rs │ └── syntax.rs └── Cargo.toml ├── .gitlab-ci.yml ├── config ├── Cargo.toml └── src │ └── lib.rs ├── Cargo.toml ├── fuzz10.sh ├── rangemap ├── Cargo.toml ├── LICENSE-MIT ├── LICENSE-APACHE └── src │ └── lib.rs ├── difftest ├── tests │ ├── config.toml │ ├── inputs │ │ ├── ub.rs │ │ ├── simple.rs │ │ └── invalid_mir.rs │ └── test.rs ├── Cargo.toml └── src │ ├── main.rs │ ├── lib.rs │ └── backends.rs ├── fuzz-one.sh ├── generate ├── Cargo.toml └── src │ ├── main.rs │ ├── literal.rs │ ├── generation │ └── intrinsics.rs │ ├── ty.rs │ ├── place_select.rs │ └── mem │ └── mod.rs ├── CITATION.cff ├── LICENSE-MIT ├── config.toml.example ├── minimise.py ├── README.md ├── LICENSE-APACHE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /config.toml -------------------------------------------------------------------------------- /abi/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod align; 2 | pub mod size; 3 | -------------------------------------------------------------------------------- /mir/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(iter_intersperse)] 2 | #![feature(box_patterns)] 3 | 4 | pub mod serialize; 5 | pub mod syntax; 6 | pub mod tyctxt; 7 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | compile: 2 | image: 3 | name: cbeuw/rustlantis-env:latest 4 | stage: build 5 | script: 6 | - cargo build 7 | - RUST_LOG=debug cargo test -------------------------------------------------------------------------------- /config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "config" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | serde = { version = "1", features = ["derive"] } 8 | toml = "0.8" 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | resolver = "2" 4 | 5 | members = [ 6 | "difftest", 7 | "mir", 8 | "generate", 9 | "rangemap", 10 | "abi", 11 | "config", 12 | ] 13 | -------------------------------------------------------------------------------- /abi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "abi" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /fuzz10.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cargo build --release 3 | 4 | export RUST_LOG=info 5 | 6 | mkdir -p out 7 | for seed in {0..10}; do 8 | target/release/generate $seed > out/$seed.rs 9 | target/release/difftest out/$seed.rs 10 | done -------------------------------------------------------------------------------- /rangemap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rangemap" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | abi = { path = "../abi" } 10 | -------------------------------------------------------------------------------- /mir/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mir" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | config = { path = "../config" } 10 | index_vec = "0.1.3" 11 | smallvec = { version = "1.10.0", features = ["const_new"] } 12 | -------------------------------------------------------------------------------- /difftest/tests/config.toml: -------------------------------------------------------------------------------- 1 | max_fn_count = 100 2 | max_args_count = 0 3 | 4 | [backends.miri] 5 | type = "miri" 6 | toolchain = "nightly" 7 | flags = ["-Zmiri-tree-borrows"] 8 | 9 | [backends.llvm] 10 | type = "llvm" 11 | toolchain = "nightly" 12 | flags = ["-Zmir-opt-level=0"] 13 | 14 | [backends.llvm-opt] 15 | type = "llvm" 16 | toolchain = "nightly" 17 | flags = ["-Zmir-opt-level=0", "-Copt-level=3", "-Ctarget-cpu=native"] 18 | -------------------------------------------------------------------------------- /difftest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "difftest" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | config = { path = "../config" } 10 | clap = "4.1.4" 11 | colored = "2.0.0" 12 | env_logger = "0.11.3" 13 | log = "0.4.17" 14 | rayon = "1.7.0" 15 | serde = { version = "1", features = ["derive"] } 16 | tempfile = "3.3.0" 17 | -------------------------------------------------------------------------------- /fuzz-one.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "$1" 4 | 5 | TMPDIR="${TMPDIR:-/tmp}" 6 | 7 | target/release/generate $1 | target/release/difftest - 8 | if [ $? -ne 0 ]; then 9 | SOURCE_DEBUG="$TMPDIR/$1-debug.rs" 10 | target/release/generate --debug $1 > $SOURCE_DEBUG 11 | 12 | REPRO_DIR="${REPRO_DIR:-repros/}" 13 | mkdir -p $REPRO_DIR 14 | 15 | if target/release/difftest $SOURCE_DEBUG 2> /dev/null; then 16 | target/release/generate $1 > "$REPRO_DIR/$1.rs" 17 | else 18 | mv $SOURCE_DEBUG $REPRO_DIR 19 | fi 20 | exit 1 21 | fi 22 | -------------------------------------------------------------------------------- /difftest/tests/inputs/ub.rs: -------------------------------------------------------------------------------- 1 | #![feature(custom_mir, core_intrinsics)] 2 | 3 | extern crate core; 4 | use core::intrinsics::mir::*; 5 | 6 | #[custom_mir(dialect = "runtime", phase = "optimized")] 7 | pub fn simple(x: i32) -> i32 { 8 | mir!( 9 | let temp1: i32; 10 | let temp2: _; 11 | 12 | { 13 | temp1 = x; 14 | Goto(exit) 15 | } 16 | 17 | exit = { 18 | temp2 = Move(temp1); 19 | Return() 20 | } 21 | ) 22 | } 23 | 24 | pub fn main() { 25 | println!("{}", simple(5)); 26 | } -------------------------------------------------------------------------------- /difftest/tests/inputs/simple.rs: -------------------------------------------------------------------------------- 1 | #![feature(custom_mir, core_intrinsics)] 2 | 3 | extern crate core; 4 | use core::intrinsics::mir::*; 5 | 6 | #[custom_mir(dialect = "runtime", phase = "optimized")] 7 | pub fn simple(x: i32) -> i32 { 8 | mir!( 9 | let temp1: i32; 10 | let temp2: _; 11 | 12 | { 13 | temp1 = x; 14 | Goto(exit) 15 | } 16 | 17 | exit = { 18 | temp2 = Move(temp1); 19 | RET = temp2; 20 | Return() 21 | } 22 | ) 23 | } 24 | 25 | pub fn main() { 26 | println!("{}", simple(5)); 27 | } -------------------------------------------------------------------------------- /difftest/tests/inputs/invalid_mir.rs: -------------------------------------------------------------------------------- 1 | #![feature(custom_mir, core_intrinsics)] 2 | 3 | extern crate core; 4 | use core::intrinsics::mir::*; 5 | 6 | #[custom_mir(dialect = "runtime", phase = "optimized")] 7 | pub fn simple(x: i32) -> i32 { 8 | mir!( 9 | let temp1: i32; 10 | let temp2: f32; 11 | 12 | { 13 | temp1 = x; 14 | Goto(exit) 15 | } 16 | 17 | exit = { 18 | temp2 = Move(temp1); 19 | RET = temp2; 20 | Return() 21 | } 22 | ) 23 | } 24 | 25 | pub fn main() { 26 | println!("{}", simple(5)); 27 | } -------------------------------------------------------------------------------- /generate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generate" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | mir = { path = "../mir" } 10 | abi = { path = "../abi" } 11 | rangemap = { path = "../rangemap" } 12 | config = { path = "../config" } 13 | petgraph = "0.6.3" 14 | rand = { version = "0.9", features = ["small_rng"] } 15 | log = "0.4.17" 16 | env_logger = "0.11.3" 17 | rand_distr = "0.5" 18 | smallvec = "1.10.0" 19 | bimap = "0.6.3" 20 | index_vec = "0.1.3" 21 | clap = { version = "4.1.4", features = ["cargo"] } 22 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: Rustlantis 3 | message: A fuzzer for the Rust compiler 4 | type: software 5 | authors: 6 | - given-names: Qian (Andy) 7 | family-names: Wang 8 | orcid: 'https://orcid.org/0009-0006-0779-8651' 9 | affiliation: ETH Zürich 10 | - given-names: Ralf 11 | family-names: Jung 12 | orcid: 'https://orcid.org/0000-0001-7669-6348' 13 | affiliation: ETH Zürich 14 | identifiers: 15 | - type: doi 16 | value: 10.1145/3689780 17 | repository-code: 'https://github.com/cbeuw/rustlantis' 18 | keywords: 19 | - Compiler testing 20 | - Rust 21 | - Fuzzing 22 | license: 23 | - Apache-2.0 24 | - MIT 25 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /rangemap/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /difftest/tests/test.rs: -------------------------------------------------------------------------------- 1 | use difftest::backends; 2 | use difftest::{Source, run_diff_test}; 3 | 4 | #[test] 5 | fn correct_mir() { 6 | let config = config::load("tests/config.toml"); 7 | let backends = backends::from_config(config); 8 | 9 | let results = run_diff_test(&Source::File("tests/inputs/simple.rs".into()), backends); 10 | println!("{}", results); 11 | assert!(results.all_same()); 12 | assert!( 13 | results["llvm"] 14 | .as_ref() 15 | .is_ok_and(|output| output.status.success() && output.stdout == "5\n") 16 | ) 17 | } 18 | 19 | #[test] 20 | fn invalid_mir() { 21 | let config = config::load("tests/config.toml"); 22 | let backends = backends::from_config(config); 23 | 24 | let results = run_diff_test( 25 | &Source::File("tests/inputs/invalid_mir.rs".into()), 26 | backends, 27 | ); 28 | println!("{}", results); 29 | assert!(results.all_same()); 30 | assert!(results["miri"].is_err()); 31 | assert_eq!(results.has_ub(), Some(false)); 32 | } 33 | 34 | #[test] 35 | fn ub() { 36 | let config = config::load("tests/config.toml"); 37 | let backends = backends::from_config(config); 38 | 39 | let results = run_diff_test(&Source::File("tests/inputs/ub.rs".into()), backends); 40 | println!("{}", results); 41 | assert_eq!(results.has_ub(), Some(true)); 42 | } 43 | -------------------------------------------------------------------------------- /generate/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | #![feature(try_blocks)] 3 | 4 | mod generation; 5 | mod literal; 6 | mod mem; 7 | mod pgraph; 8 | mod place_select; 9 | mod ty; 10 | 11 | use std::time::Instant; 12 | 13 | use clap::{Arg, arg, command, value_parser}; 14 | use log::{debug, info}; 15 | 16 | use crate::generation::GenerationCtx; 17 | 18 | fn main() { 19 | env_logger::init(); 20 | let matches = command!() 21 | .args(&[ 22 | arg!(-d --debug "generate a program where values are printed instead of hashed (slow)"), 23 | Arg::new("call-syntax") 24 | .long("call-syntax") 25 | .value_parser(["v1", "v2", "v3", "v4"]) 26 | .default_value("v4") 27 | .help("switch between different versions of Call syntaxes"), 28 | arg!( "generation seed").value_parser(value_parser!(u64)), 29 | ]) 30 | .get_matches(); 31 | 32 | let seed: u64 = *matches 33 | .get_one::("seed") 34 | .expect("need an integer as seed"); 35 | info!("Generating a program with seed {seed}"); 36 | let debug_dump = matches.get_one::("debug").copied().unwrap_or(false); 37 | let config = config::load("config.toml"); 38 | let call_syntax = matches.get_one::("call-syntax").unwrap(); 39 | let genctxt = GenerationCtx::new(config, seed, debug_dump); 40 | let time = Instant::now(); 41 | let (program, tcx) = genctxt.generate(); 42 | println!("{}", program.serialize(&tcx, call_syntax.as_str().into())); 43 | println!("{}", tcx.serialize()); 44 | let dur = time.elapsed(); 45 | debug!("took {}s to generate", dur.as_secs_f32()); 46 | } 47 | -------------------------------------------------------------------------------- /config.toml.example: -------------------------------------------------------------------------------- 1 | bb_max_len = 32 2 | max_switch_targets = 8 3 | max_bb_count = 50 4 | max_bb_count_hard = 100 5 | max_fn_count = 20 6 | max_args_count = 12 7 | var_dump_chance = 0.5 8 | tuple_max_len = 4 9 | array_max_len = 8 10 | struct_max_fields = 8 11 | adt_max_variants = 4 12 | composite_count = 64 13 | adt_count = 8 14 | 15 | [backends.miri] 16 | type = "miri" 17 | toolchain = "nightly" 18 | flags = ["-Zmiri-tree-borrows"] 19 | 20 | [backends.miri-opt] 21 | type = "miri" 22 | toolchain = "nightly" 23 | flags = ["-Zmiri-tree-borrows", "-Zmir-opt-level=4"] 24 | 25 | [backends.miri-unchecked] 26 | type = "miri" 27 | toolchain = "nightly" 28 | flags = ["-Zmiri-disable-stacked-borrows", "-Zmiri-disable-validation", "-Zmiri-disable-alignment-check"] 29 | 30 | [backends.miri-repo] 31 | type = "miri-repo" 32 | path = "/home/ben/miri" 33 | flags = ["-Zmiri-tree-borrows"] 34 | 35 | [backends.llvm] 36 | type = "llvm" 37 | toolchain = "nightly" 38 | flags = ["-Zmir-opt-level=0"] 39 | 40 | [backends.llvm-opt] 41 | type = "llvm" 42 | toolchain = "nightly" 43 | flags = ["-Zmir-opt-level=0", "-Copt-level=3", "-Ctarget-cpu=native"] 44 | 45 | [backends.cranelift] 46 | type = "cranelift" 47 | toolchain = "nightly" 48 | flags = ["-Zmir-opt-level=0", "-Copt-level=3"] 49 | 50 | [backends.cranelift-repo] 51 | type = "cranelift-repo" 52 | path = "/home/ben/rustc_codegen_cranelift" 53 | flags = ["-Zmir-opt-level=0", "-Copt-level=3"] 54 | 55 | [backends.cranelift-binary] 56 | type = "cranelift-binary" 57 | path = "/usr/local/bin/clif" 58 | flags = ["-Zmir-opt-level=0", "-Copt-level=3"] 59 | 60 | [backends.gcc] 61 | type = "gcc" 62 | path = "/home/ben/rustc_codegen_gcc" 63 | flags = ["-Zmir-opt-level=0", "-Copt-level=3"] 64 | -------------------------------------------------------------------------------- /difftest/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(iter_intersperse)] 2 | 3 | use std::{ 4 | io::{self, Read}, 5 | path::PathBuf, 6 | process::ExitCode, 7 | str::FromStr, 8 | }; 9 | 10 | use clap::{Arg, Command}; 11 | use difftest::backends; 12 | use difftest::{Source, run_diff_test}; 13 | use log::{debug, error, info}; 14 | 15 | fn main() -> ExitCode { 16 | env_logger::init(); 17 | 18 | let matches = Command::new("difftest") 19 | .arg(Arg::new("file").required(true)) 20 | .get_matches(); 21 | let source = matches.get_one::("file").expect("required"); 22 | 23 | let config_path = std::env::var("RUSTLANTIS_CONFIG").unwrap_or("config.toml".to_string()); 24 | let config = config::load(config_path); 25 | let backends = backends::from_config(config); 26 | 27 | let source = if source == "-" { 28 | let mut code = String::new(); 29 | io::stdin() 30 | .read_to_string(&mut code) 31 | .expect("can read source code from stdin"); 32 | Source::Stdin(code) 33 | } else { 34 | Source::File(PathBuf::from_str(source).expect("is valid path")) 35 | }; 36 | 37 | info!( 38 | "Difftesting {} with {}", 39 | source, 40 | backends 41 | .keys() 42 | .map(String::as_str) 43 | .intersperse(", ") 44 | .collect::() 45 | ); 46 | 47 | let results = run_diff_test(&source, backends); 48 | if results.all_same() && results.all_success() { 49 | info!("{} is all the same", source); 50 | debug!("{}", results); 51 | ExitCode::SUCCESS 52 | } else { 53 | let results = results.to_string(); 54 | error!("{} didn't pass:\n{results}", source); 55 | ExitCode::FAILURE 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /minimise.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import subprocess 5 | import string 6 | 7 | end_bb = "Return()" 8 | 9 | def check(file: os.PathLike) -> bool: 10 | out = subprocess.run(["timeout", "5", "target/release/difftest", str(file)], capture_output=True) 11 | if out.returncode == 124: 12 | return False 13 | err = out.stderr.decode(encoding = 'utf-8') 14 | return "didn't pass" in err and "stderr" not in err 15 | 16 | def mutate(orig: str) -> str: 17 | if len(orig) == 0: 18 | return orig 19 | 20 | if orig[0] in string.ascii_uppercase and orig.endswith(")") and orig != end_bb: 21 | return end_bb 22 | else: 23 | return f"//{orig}" 24 | 25 | 26 | if __name__ == "__main__": 27 | with open("minimised.rs", "w", encoding='utf-8') as working: 28 | with open("repro.rs", "r", encoding='utf-8') as orig: 29 | source = orig.readlines() 30 | 31 | progress = True 32 | while progress: 33 | progress = False 34 | limit = len(source)-1 35 | for line in reversed(range(limit)): 36 | saved = source[line].strip() 37 | if saved.startswith("//"): 38 | continue 39 | elif len(saved) == 0: 40 | continue 41 | elif saved.endswith("{"): 42 | continue 43 | elif saved == "}": 44 | continue 45 | elif saved.startswith("#"): 46 | continue 47 | 48 | print(line, end='\r') 49 | 50 | source[line] = mutate(saved) + "\n" 51 | 52 | working.seek(0) 53 | working.writelines(source) 54 | working.truncate() 55 | working.flush() 56 | 57 | if check(working.name): 58 | progress = True 59 | else: 60 | source[line] = saved + "\n" 61 | 62 | working.seek(0) 63 | working.writelines(source) 64 | working.truncate() 65 | working.flush() 66 | print(f"done pass") -------------------------------------------------------------------------------- /abi/src/align.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright: The Rust Project contributors 3 | 4 | Permission is hereby granted, free of charge, to any 5 | person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the 7 | Software without restriction, including without 8 | limitation the rights to use, copy, modify, merge, 9 | publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software 11 | is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice 15 | shall be included in all copies or substantial portions 16 | of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 19 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 20 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 21 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 22 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 25 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | */ 28 | use std::fmt; 29 | 30 | use super::size::Size; 31 | 32 | /// Alignment of a type in bytes (always a power of two). 33 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 34 | pub struct Align { 35 | pow2: u8, 36 | } 37 | 38 | // This is debug-printed a lot in larger structs, don't waste too much space there 39 | impl fmt::Debug for Align { 40 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 41 | write!(f, "Align({} bytes)", self.bytes()) 42 | } 43 | } 44 | 45 | impl Align { 46 | pub const ONE: Align = Align { pow2: 0 }; 47 | pub const MAX: Align = Align { pow2: 29 }; 48 | 49 | #[inline] 50 | pub fn from_bits(bits: u64) -> Result { 51 | Align::from_bytes(Size::from_bits(bits).bytes()) 52 | } 53 | 54 | #[inline] 55 | pub fn from_bytes(align: u64) -> Result { 56 | // Treat an alignment of 0 bytes like 1-byte alignment. 57 | if align == 0 { 58 | return Ok(Align::ONE); 59 | } 60 | 61 | #[cold] 62 | fn not_power_of_2(align: u64) -> String { 63 | format!("`{}` is not a power of 2", align) 64 | } 65 | 66 | #[cold] 67 | fn too_large(align: u64) -> String { 68 | format!("`{}` is too large", align) 69 | } 70 | 71 | let mut bytes = align; 72 | let mut pow2: u8 = 0; 73 | while (bytes & 1) == 0 { 74 | pow2 += 1; 75 | bytes >>= 1; 76 | } 77 | if bytes != 1 { 78 | return Err(not_power_of_2(align)); 79 | } 80 | if pow2 > Self::MAX.pow2 { 81 | return Err(too_large(align)); 82 | } 83 | 84 | Ok(Align { pow2 }) 85 | } 86 | 87 | #[inline] 88 | pub fn bytes(self) -> u64 { 89 | 1 << self.pow2 90 | } 91 | 92 | #[inline] 93 | pub fn bits(self) -> u64 { 94 | self.bytes() * 8 95 | } 96 | 97 | /// Computes the best alignment possible for the given offset 98 | /// (the largest power of two that the offset is a multiple of). 99 | /// 100 | /// N.B., for an offset of `0`, this happens to return `2^64`. 101 | #[inline] 102 | pub fn max_for_offset(offset: Size) -> Align { 103 | Align { 104 | pow2: offset.bytes().trailing_zeros() as u8, 105 | } 106 | } 107 | 108 | /// Lower the alignment, if necessary, such that the given offset 109 | /// is aligned to it (the offset is a multiple of the alignment). 110 | #[inline] 111 | pub fn restrict_for_offset(self, offset: Size) -> Align { 112 | self.min(Align::max_for_offset(offset)) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /config/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use std::collections::HashMap; 3 | use std::path::Path; 4 | 5 | pub fn load(path: impl AsRef) -> Config { 6 | let config = std::fs::read_to_string(path).unwrap(); 7 | toml::from_str(&config).unwrap() 8 | } 9 | 10 | #[derive(Deserialize, Clone)] 11 | pub struct Config { 12 | #[serde(flatten)] 13 | pub generation: GenerationConfig, 14 | #[serde(flatten)] 15 | pub ty: TyConfig, 16 | pub backends: HashMap, 17 | } 18 | 19 | #[derive(Deserialize, Clone)] 20 | #[serde(rename_all = "kebab-case")] 21 | #[serde(tag = "type")] 22 | pub enum BackendConfig { 23 | Miri { 24 | toolchain: String, 25 | flags: Vec, 26 | }, 27 | MiriRepo { 28 | path: String, 29 | flags: Vec, 30 | }, 31 | #[serde(rename = "llvm")] 32 | LLVM { 33 | toolchain: String, 34 | flags: Vec, 35 | }, 36 | Cranelift { 37 | toolchain: String, 38 | flags: Vec, 39 | }, 40 | CraneliftRepo { 41 | path: String, 42 | flags: Vec, 43 | }, 44 | CraneliftBinary { 45 | path: String, 46 | flags: Vec, 47 | }, 48 | #[serde(rename = "gcc")] 49 | GCC { 50 | path: String, 51 | flags: Vec, 52 | }, 53 | } 54 | 55 | #[derive(Deserialize, Clone)] 56 | pub struct GenerationConfig { 57 | /// Max. number of statements & declarations in a bb 58 | #[serde(default = "bb_max_len")] 59 | pub bb_max_len: usize, 60 | 61 | /// Max. number of switch targets in a SwitchInt terminator 62 | #[serde(default = "max_switch_targets")] 63 | pub max_switch_targets: usize, 64 | 65 | /// Max. number of BB in a function if RET is init (a Return must be generated) 66 | #[serde(default = "max_bb_count")] 67 | pub max_bb_count: usize, 68 | 69 | /// Max. number of BB in a function before giving up this function 70 | #[serde(default = "max_bb_count_hard")] 71 | pub max_bb_count_hard: usize, 72 | 73 | /// Max. number of functions in the program Call generator stops being a possible candidate 74 | #[serde(default = "max_fn_count")] 75 | pub max_fn_count: usize, 76 | 77 | /// Max. number of arguments a function can have 78 | #[serde(default = "max_args_count")] 79 | pub max_args_count: usize, 80 | 81 | /// Expected proportion of variables to be dumped 82 | #[serde(default = "var_dump_chance")] 83 | pub var_dump_chance: f32, 84 | } 85 | 86 | fn bb_max_len() -> usize { 87 | 32 88 | } 89 | 90 | fn max_switch_targets() -> usize { 91 | 8 92 | } 93 | 94 | fn max_bb_count() -> usize { 95 | 50 96 | } 97 | 98 | fn max_bb_count_hard() -> usize { 99 | 100 100 | } 101 | 102 | fn max_fn_count() -> usize { 103 | 20 104 | } 105 | 106 | fn max_args_count() -> usize { 107 | 12 108 | } 109 | 110 | fn var_dump_chance() -> f32 { 111 | 0.5 112 | } 113 | 114 | #[derive(Deserialize, Clone)] 115 | pub struct TyConfig { 116 | /// Max. arity of tuple 117 | #[serde(default = "tuple_max_len")] 118 | pub tuple_max_len: usize, 119 | 120 | /// Max. len of array 121 | #[serde(default = "array_max_len")] 122 | pub array_max_len: usize, 123 | 124 | /// Max. number of fields in a struct or enum variant 125 | #[serde(default = "struct_max_fields")] 126 | pub struct_max_fields: usize, 127 | 128 | /// Max. number of variants in an enum 129 | #[serde(default = "adt_max_variants")] 130 | pub adt_max_variants: usize, 131 | 132 | /// Number of composite structural types 133 | #[serde(default = "composite_count")] 134 | pub composite_count: usize, 135 | 136 | /// Number of ADTs 137 | #[serde(default = "adt_count")] 138 | pub adt_count: usize, 139 | } 140 | 141 | fn tuple_max_len() -> usize { 142 | 4 143 | } 144 | fn array_max_len() -> usize { 145 | 8 146 | } 147 | 148 | fn struct_max_fields() -> usize { 149 | 8 150 | } 151 | 152 | fn adt_max_variants() -> usize { 153 | 4 154 | } 155 | 156 | fn composite_count() -> usize { 157 | 64 158 | } 159 | 160 | fn adt_count() -> usize { 161 | 8 162 | } 163 | 164 | impl Default for TyConfig { 165 | fn default() -> Self { 166 | Self { 167 | tuple_max_len: 4, 168 | array_max_len: 8, 169 | struct_max_fields: 8, 170 | adt_max_variants: 4, 171 | composite_count: 64, 172 | adt_count: 8, 173 | } 174 | } 175 | } 176 | 177 | #[test] 178 | fn example_parses() { 179 | let _ = load("../config.toml.example"); 180 | } 181 | -------------------------------------------------------------------------------- /mir/src/tyctxt.rs: -------------------------------------------------------------------------------- 1 | use config::TyConfig; 2 | use std::{collections::HashMap, slice}; 3 | 4 | use index_vec::IndexVec; 5 | 6 | use crate::{ 7 | serialize::Serialize, 8 | syntax::{Adt, TyId, TyKind}, 9 | }; 10 | 11 | #[derive(Debug, Clone, Copy)] 12 | pub struct AdtMeta { 13 | pub copy: bool, 14 | } 15 | 16 | impl AdtMeta { 17 | fn derive_attrs(&self) -> String { 18 | let mut attrs = vec!["Debug"]; 19 | if self.copy { 20 | attrs.push("Copy"); 21 | attrs.push("Clone"); 22 | } 23 | 24 | let list: String = attrs.iter().intersperse(&",").copied().collect(); 25 | if list.is_empty() { 26 | "".to_owned() 27 | } else { 28 | format!("#[derive({list})]\n") 29 | } 30 | } 31 | } 32 | 33 | pub struct TyCtxt { 34 | tys: IndexVec, 35 | adt_meta: HashMap, 36 | pub config: TyConfig, 37 | } 38 | 39 | impl TyCtxt { 40 | pub const UNIT: TyId = TyId::from_usize_unchecked(0); 41 | pub const BOOL: TyId = TyId::from_usize_unchecked(1); 42 | pub const CHAR: TyId = TyId::from_usize_unchecked(2); 43 | pub const ISIZE: TyId = TyId::from_usize_unchecked(3); 44 | pub const I8: TyId = TyId::from_usize_unchecked(4); 45 | pub const I16: TyId = TyId::from_usize_unchecked(5); 46 | pub const I32: TyId = TyId::from_usize_unchecked(6); 47 | pub const I64: TyId = TyId::from_usize_unchecked(7); 48 | pub const I128: TyId = TyId::from_usize_unchecked(8); 49 | pub const USIZE: TyId = TyId::from_usize_unchecked(9); 50 | pub const U8: TyId = TyId::from_usize_unchecked(10); 51 | pub const U16: TyId = TyId::from_usize_unchecked(11); 52 | pub const U32: TyId = TyId::from_usize_unchecked(12); 53 | pub const U64: TyId = TyId::from_usize_unchecked(13); 54 | pub const U128: TyId = TyId::from_usize_unchecked(14); 55 | pub const F32: TyId = TyId::from_usize_unchecked(15); 56 | pub const F64: TyId = TyId::from_usize_unchecked(16); 57 | 58 | pub fn from_primitives(config: TyConfig) -> Self { 59 | let primitives: [TyKind; 17] = [ 60 | TyKind::Unit, 61 | TyKind::Bool, 62 | TyKind::Char, 63 | TyKind::ISIZE, 64 | TyKind::I8, 65 | TyKind::I16, 66 | TyKind::I32, 67 | TyKind::I64, 68 | TyKind::I128, 69 | TyKind::USIZE, 70 | TyKind::U8, 71 | TyKind::U16, 72 | TyKind::U32, 73 | TyKind::U64, 74 | TyKind::U128, 75 | TyKind::F32, 76 | TyKind::F64, 77 | ]; 78 | let tys = IndexVec::from_iter(primitives); 79 | Self { 80 | tys, 81 | adt_meta: HashMap::new(), 82 | config, 83 | } 84 | } 85 | 86 | pub fn push(&mut self, kind: TyKind) -> TyId { 87 | assert!(kind.is_structural()); 88 | self.tys.push(kind) 89 | } 90 | 91 | pub fn push_adt(&mut self, adt: Adt, meta: AdtMeta) -> TyId { 92 | let id = self.tys.push(TyKind::Adt(adt)); 93 | self.adt_meta.insert(id, meta); 94 | id 95 | } 96 | 97 | pub fn meta(&self, ty: TyId) -> AdtMeta { 98 | self.adt_meta[&ty] 99 | } 100 | 101 | pub fn kind(&self, ty: TyId) -> &TyKind { 102 | &self.tys[ty] 103 | } 104 | 105 | pub fn indices(&self) -> impl Iterator { 106 | self.tys.indices() 107 | } 108 | 109 | pub fn iter(&self) -> slice::Iter<'_, TyKind> { 110 | self.tys.iter() 111 | } 112 | 113 | pub fn iter_enumerated(&self) -> impl Iterator { 114 | self.tys.iter_enumerated() 115 | } 116 | 117 | pub fn len(&self) -> usize { 118 | self.tys.len() 119 | } 120 | 121 | pub fn serialize(&self) -> String { 122 | let mut str = String::new(); 123 | for (id, adt) in self.tys.iter_enumerated().filter(|(_, kind)| kind.is_adt()) { 124 | let TyKind::Adt(adt) = adt else { 125 | panic!("not an adt"); 126 | }; 127 | str += &self.adt_meta[&id].derive_attrs(); 128 | if adt.is_enum() { 129 | let variants: String = adt 130 | .variants 131 | .iter_enumerated() 132 | .map(|(vid, def)| { 133 | format!("{}{{\n{}\n}}", vid.identifier(), def.serialize(self)) 134 | }) 135 | .intersperse(",\n".to_string()) 136 | .collect(); 137 | str += &format!("pub enum {} {{\n{variants}}}\n", id.type_name()) 138 | } else { 139 | let def = adt.variants.first().expect("has only one variant"); 140 | str += &format!( 141 | "pub struct {} {{\n{}}}\n", 142 | id.type_name(), 143 | def.serialize(self) 144 | ) 145 | } 146 | } 147 | str 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /difftest/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(iter_intersperse)] 2 | 3 | pub mod backends; 4 | 5 | use std::{ 6 | collections::{HashMap, HashSet}, 7 | fmt::{self, Display}, 8 | ops::Index, 9 | path::PathBuf, 10 | time::Instant, 11 | }; 12 | 13 | use backends::{Backend, CompExecError, ExecResult}; 14 | use colored::Colorize; 15 | use log::{debug, log_enabled}; 16 | use rayon::prelude::{IntoParallelIterator, ParallelIterator}; 17 | 18 | pub enum Source { 19 | File(PathBuf), 20 | Stdin(String), 21 | } 22 | 23 | impl Display for Source { 24 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 | match self { 26 | Source::File(path) => f.write_str(&path.to_string_lossy()), 27 | Source::Stdin(_) => f.write_str("[stdin]"), 28 | } 29 | } 30 | } 31 | 32 | pub struct ExecResults { 33 | // Equivalence classes of exec results and backends 34 | results: HashMap>, 35 | } 36 | 37 | impl ExecResults { 38 | fn from_exec_results<'a>(map: impl Iterator) -> Self { 39 | //TODO: optimisation here to check if all results are equal directly, since most should be 40 | 41 | // Split execution results into equivalent classes 42 | let mut eq_classes: HashMap> = HashMap::new(); 43 | 44 | 'outer: for (name, result) in map { 45 | for (class_result, names) in &mut eq_classes { 46 | // Put into an existing equivalence class 47 | let eq = if let Ok(class_out) = class_result 48 | && let Ok(ref out) = result 49 | { 50 | class_out.stdout == out.stdout 51 | } else { 52 | result == *class_result 53 | }; 54 | if eq { 55 | names.insert(name); 56 | continue 'outer; 57 | } 58 | } 59 | 60 | // No equal execution result, make a new class 61 | eq_classes.insert(result.clone(), HashSet::from([name])); 62 | } 63 | 64 | Self { 65 | results: eq_classes, 66 | } 67 | } 68 | 69 | pub fn all_same(&self) -> bool { 70 | self.results.len() == 1 71 | } 72 | 73 | pub fn all_success(&self) -> bool { 74 | self.results.keys().all(|r| r.is_ok()) 75 | } 76 | 77 | pub fn has_ub(&self) -> Option { 78 | self.results 79 | .iter() 80 | .find_map(|(result, backends)| { 81 | if backends.contains("miri") { 82 | Some(result) 83 | } else { 84 | None 85 | } 86 | }) 87 | .map(|result| { 88 | result.clone().is_err_and(|err| { 89 | err.0 90 | .stderr 91 | .to_string_lossy() 92 | .contains("Undefined Behavior") 93 | }) 94 | }) 95 | } 96 | 97 | pub fn miri_result(&self) -> Option<&ExecResult> { 98 | self.results.iter().find_map(|(result, backends)| { 99 | if backends.contains("miri") { 100 | Some(result) 101 | } else { 102 | None 103 | } 104 | }) 105 | } 106 | } 107 | 108 | impl Index<&str> for ExecResults { 109 | type Output = ExecResult; 110 | 111 | fn index(&self, index: &str) -> &Self::Output { 112 | for (result, names) in &self.results { 113 | if names.contains(index) { 114 | return result; 115 | } 116 | } 117 | panic!("no result for {index}") 118 | } 119 | } 120 | 121 | impl fmt::Display for ExecResults { 122 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 123 | for (result, names) in &self.results { 124 | f.write_fmt(format_args!( 125 | "{} produced the following output:\n", 126 | names 127 | .iter() 128 | .map(String::as_str) 129 | .intersperse(", ") 130 | .collect::() 131 | .blue() 132 | ))?; 133 | match result { 134 | Ok(out) => { 135 | f.write_fmt(format_args!("stdout:\n{}", out.stdout.to_string_lossy()))?; 136 | } 137 | Err(CompExecError(out)) => { 138 | f.write_fmt(format_args!("status: {}\n", out.status))?; 139 | f.write_fmt(format_args!( 140 | "stdout:\n{}================\n", 141 | out.stdout.to_string_lossy() 142 | ))?; 143 | f.write_fmt(format_args!( 144 | "{}:\n{}================\n", 145 | "stderr".red(), 146 | out.stderr.to_string_lossy() 147 | ))?; 148 | } 149 | } 150 | } 151 | Ok(()) 152 | } 153 | } 154 | 155 | pub fn run_diff_test<'a>( 156 | source: &Source, 157 | backends: HashMap>, 158 | ) -> ExecResults { 159 | let target_dir = tempfile::tempdir().unwrap(); 160 | let exec_results: HashMap = backends 161 | .into_par_iter() 162 | .map(|(name, b)| { 163 | let target_path = target_dir.path().join(&name); 164 | let result = if log_enabled!(log::Level::Debug) { 165 | let time = Instant::now(); 166 | let result = b.execute(source, &target_path); 167 | let dur = time.elapsed(); 168 | debug!("{name} took {}s", dur.as_secs_f32()); 169 | result 170 | } else { 171 | b.execute(source, &target_path) 172 | }; 173 | (name.clone(), result) 174 | }) 175 | .collect(); 176 | 177 | ExecResults::from_exec_results(exec_results.into_iter()) 178 | } 179 | -------------------------------------------------------------------------------- /generate/src/literal.rs: -------------------------------------------------------------------------------- 1 | use mir::{ 2 | syntax::{FloatTy, IntTy, Literal, TyId, TyKind, UintTy}, 3 | tyctxt::TyCtxt, 4 | }; 5 | use rand::{Rng, RngCore, seq::IndexedRandom}; 6 | use rand_distr::Distribution; 7 | 8 | struct Sombrero; 9 | 10 | impl Distribution for Sombrero { 11 | fn sample(&self, rng: &mut R) -> usize { 12 | match rng.random_range(0..=1) { 13 | 0 => rng.random_range(0..8), // FIXME: ARRAY_MAX_LEN 14 | 1 => rng.random_range(usize::MIN..=usize::MAX), 15 | _ => unreachable!(), 16 | } 17 | } 18 | } 19 | 20 | impl Distribution for Sombrero { 21 | fn sample(&self, rng: &mut R) -> isize { 22 | match rng.random_range(0..=2) { 23 | 0 => rng.random_range(-128i32..=127i32) as isize, 24 | 1 => isize::MIN, 25 | 2 => isize::MAX, 26 | _ => unreachable!(), 27 | } 28 | } 29 | } 30 | 31 | pub trait GenLiteral: Rng { 32 | fn is_literalble(ty: TyId, tcx: &TyCtxt) -> bool { 33 | match ty.kind(tcx) { 34 | TyKind::Unit => false, 35 | _ => ty.is_scalar(tcx), 36 | } 37 | } 38 | fn gen_literal(&mut self, ty: TyId, tcx: &TyCtxt) -> Option { 39 | let lit: Literal = match ty.kind(tcx) { 40 | TyKind::Bool => self.random_bool(0.5).into(), 41 | TyKind::Char => { 42 | // There are 0xD7FF + 1 Unicode Scalar Values in the lower range, and 0x10FFFF - 0xE000 + 1 43 | // values in the upper range. 44 | let ordinal = self.random_range(0..((0xD7FF + 1) + (0x10FFFF - 0xE000 + 1))); 45 | if ordinal <= 0xD7FF { 46 | char::from_u32(ordinal).unwrap().into() 47 | } else { 48 | char::from_u32(ordinal - 0xD800 + 0xE000).unwrap().into() 49 | } 50 | } 51 | TyKind::Uint(UintTy::Usize) => { 52 | let i: usize = Sombrero.sample(self); 53 | i.try_into().expect("usize isn't greater than 128 bits") 54 | } 55 | TyKind::Uint(UintTy::U8) => self.random_range(u8::MIN..=u8::MAX).into(), 56 | TyKind::Uint(UintTy::U16) => self.random_range(u16::MIN..=u16::MAX).into(), 57 | TyKind::Uint(UintTy::U32) => self.random_range(u32::MIN..=u32::MAX).into(), 58 | TyKind::Uint(UintTy::U64) => self.random_range(u64::MIN..=u64::MAX).into(), 59 | TyKind::Uint(UintTy::U128) => self.random_range(u128::MIN..=u128::MAX).into(), 60 | TyKind::Int(IntTy::Isize) => { 61 | let i: isize = Sombrero.sample(self); 62 | i.try_into().expect("isize isn't greater than 128 bits") 63 | } 64 | TyKind::Int(IntTy::I8) => self.random_range(i8::MIN..=i8::MAX).into(), 65 | TyKind::Int(IntTy::I16) => self.random_range(i16::MIN..=i16::MAX).into(), 66 | TyKind::Int(IntTy::I32) => self.random_range(i32::MIN..=i32::MAX).into(), 67 | TyKind::Int(IntTy::I64) => self.random_range(i64::MIN..=i64::MAX).into(), 68 | TyKind::Int(IntTy::I128) => self.random_range(i128::MIN..=i128::MAX).into(), 69 | TyKind::Float(FloatTy::F32) => generate_f32(self).into(), 70 | TyKind::Float(FloatTy::F64) => generate_f64(self).into(), 71 | _ => return None, 72 | }; 73 | Some(lit) 74 | } 75 | fn gen_literal_non_zero(&mut self, ty: TyId, tcx: &TyCtxt) -> Option { 76 | self.gen_literal(ty, tcx).map(|lit| match lit { 77 | Literal::Uint(n, t) => { 78 | if n == 0 { 79 | Literal::Uint(n + 1, t) 80 | } else { 81 | lit 82 | } 83 | } 84 | Literal::Int(n, t) => { 85 | if n == 0 { 86 | Literal::Int(n + 1, t) 87 | } else { 88 | lit 89 | } 90 | } 91 | Literal::Float(n, t) => { 92 | if n == 0. { 93 | Literal::Float(n + 1., t) 94 | } else { 95 | lit 96 | } 97 | } 98 | _ => lit, 99 | }) 100 | } 101 | } 102 | 103 | impl GenLiteral for R {} 104 | 105 | enum Category { 106 | Normal, 107 | Subnormal, 108 | Zero, 109 | Infinity, 110 | NaN, 111 | } 112 | 113 | const FLOAT_CATEGORIES: [Category; 5] = [ 114 | Category::Normal, 115 | Category::Subnormal, 116 | Category::Zero, 117 | Category::Infinity, 118 | Category::NaN, 119 | ]; 120 | 121 | fn generate_f32(rng: &mut R) -> f32 { 122 | let chosen = FLOAT_CATEGORIES.choose(rng).unwrap(); 123 | match chosen { 124 | Category::Normal => { 125 | let sign: u32 = *[0 << 31, 1 << 31].choose(rng).unwrap(); 126 | let exponent = rng.random_range(0x01..=0xfe); 127 | let fraction = rng.random_range(0..(1 << 23)); 128 | f32::from_bits(sign | exponent | fraction) 129 | } 130 | Category::Subnormal => { 131 | let sign: u32 = *[0 << 31, 1 << 31].choose(rng).unwrap(); 132 | let exponent = 0 << 23; 133 | let fraction = rng.random_range(1..(1 << 23)); 134 | f32::from_bits(sign | exponent | fraction) 135 | } 136 | Category::Zero => *[0.0, -0.0].choose(rng).unwrap(), 137 | Category::Infinity => *[f32::INFINITY, f32::NEG_INFINITY].choose(rng).unwrap(), 138 | Category::NaN => f32::NAN, 139 | } 140 | } 141 | 142 | fn generate_f64(rng: &mut R) -> f64 { 143 | let chosen = FLOAT_CATEGORIES.choose(rng).unwrap(); 144 | match chosen { 145 | Category::Normal => { 146 | let sign: u64 = *[0 << 63, 1 << 63].choose(rng).unwrap(); 147 | let exponent = rng.random_range(0x001..=0x7fe); 148 | let fraction = rng.random_range(0..(1 << 52)); 149 | f64::from_bits(sign | exponent | fraction) 150 | } 151 | Category::Subnormal => { 152 | let sign: u64 = *[0 << 63, 1 << 63].choose(rng).unwrap(); 153 | let exponent = 0 << 52; 154 | let fraction = rng.random_range(1..(1 << 52)); 155 | f64::from_bits(sign | exponent | fraction) 156 | } 157 | Category::Zero => *[0.0, -0.0].choose(rng).unwrap(), 158 | Category::Infinity => *[f64::INFINITY, f64::NEG_INFINITY].choose(rng).unwrap(), 159 | Category::NaN => f64::NAN, 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /generate/src/generation/intrinsics.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::BorrowMut; 2 | 3 | use mir::{ 4 | syntax::{Callee, Mutability, Operand, Place, TyId, TyKind}, 5 | tyctxt::TyCtxt, 6 | }; 7 | use rand::{Rng, seq::IteratorRandom}; 8 | 9 | use crate::{literal::GenLiteral, mem::BasicMemory, place_select::PlaceSelector}; 10 | 11 | use super::{GenerationCtx, Result, SelectionError}; 12 | 13 | pub trait CoreIntrinsic { 14 | fn name(&self) -> &'static str; 15 | 16 | fn dest_type(&self, ty: TyId, tcx: &TyCtxt) -> bool; 17 | 18 | fn choose_operands(&self, ctx: &GenerationCtx, dest: &Place) -> Option>; 19 | 20 | fn generate_terminator( 21 | &self, 22 | ctx: &GenerationCtx, 23 | dest: &Place, 24 | ) -> Result<(Callee, Vec)> { 25 | if !self.dest_type(dest.ty(ctx.current_decls(), &ctx.tcx), &ctx.tcx) { 26 | return Err(SelectionError::Exhausted); 27 | } 28 | let args = self 29 | .choose_operands(ctx, dest) 30 | .ok_or(SelectionError::Exhausted)?; 31 | Ok((Callee::Intrinsic(self.name()), args)) 32 | } 33 | } 34 | 35 | struct Fmaf64; 36 | impl CoreIntrinsic for Fmaf64 { 37 | fn name(&self) -> &'static str { 38 | "fmaf64" 39 | } 40 | 41 | fn dest_type(&self, ty: TyId, _: &TyCtxt) -> bool { 42 | ty == TyCtxt::F64 43 | } 44 | 45 | fn choose_operands(&self, ctx: &GenerationCtx, dest: &Place) -> Option> { 46 | let a = ctx.choose_operand(&[TyCtxt::F64], dest).ok()?; 47 | let b = ctx.choose_operand(&[TyCtxt::F64], dest).ok()?; 48 | let c = ctx.choose_operand(&[TyCtxt::F64], dest).ok()?; 49 | Some(vec![a, b, c]) 50 | } 51 | } 52 | 53 | pub(super) struct ArithOffset; 54 | impl CoreIntrinsic for ArithOffset { 55 | fn name(&self) -> &'static str { 56 | "arith_offset" 57 | } 58 | 59 | fn dest_type(&self, ty: TyId, tcx: &TyCtxt) -> bool { 60 | matches!(ty.kind(tcx), TyKind::RawPtr(.., Mutability::Not)) 61 | } 62 | 63 | fn choose_operands(&self, ctx: &GenerationCtx, dest: &Place) -> Option> { 64 | let (ptrs, weights) = PlaceSelector::for_offsetee(ctx.tcx.clone()) 65 | .of_ty(dest.ty(ctx.current_decls(), &ctx.tcx)) 66 | .except(dest) 67 | .into_weighted(&ctx.pt)?; 68 | let ptr = ctx 69 | .make_choice_weighted(ptrs.into_iter(), weights, |ppath| { 70 | Ok(ppath.to_place(&ctx.pt)) 71 | }) 72 | .ok()?; 73 | 74 | let offset = ctx.pt.get_offset(&ptr); 75 | 76 | let mut rng = ctx.rng.borrow_mut(); 77 | let new_offset = match offset { 78 | // Don't break roundtripped pointer 79 | Some(0) => { 80 | return None; 81 | } 82 | Some(existing) if existing.checked_neg().is_some() && rng.random_bool(0.5) => { 83 | Operand::Constant((-existing).try_into().unwrap()) 84 | } 85 | _ => PlaceSelector::for_known_val(ctx.tcx.clone()) 86 | .of_ty(TyCtxt::ISIZE) 87 | .into_iter_place(&ctx.pt) 88 | .choose(&mut *rng) 89 | .map(Operand::Copy) 90 | .unwrap_or_else(|| { 91 | Operand::Constant( 92 | rng.borrow_mut() 93 | .gen_literal(TyCtxt::ISIZE, &ctx.tcx) 94 | .expect("can generate a literal"), 95 | ) 96 | }), 97 | }; 98 | 99 | Some(vec![Operand::Copy(ptr), new_offset]) 100 | } 101 | } 102 | 103 | struct Bswap; 104 | impl CoreIntrinsic for Bswap { 105 | fn name(&self) -> &'static str { 106 | "bswap" 107 | } 108 | 109 | fn dest_type(&self, ty: TyId, tcx: &TyCtxt) -> bool { 110 | matches!(ty.kind(tcx), TyKind::Int(..) | TyKind::Uint(..)) 111 | } 112 | 113 | fn choose_operands(&self, ctx: &GenerationCtx, dest: &Place) -> Option> { 114 | let arg = ctx 115 | .choose_operand(&[dest.ty(ctx.current_decls(), &ctx.tcx)], dest) 116 | .ok()?; 117 | Some(vec![arg]) 118 | } 119 | } 120 | 121 | pub(super) struct Transmute; 122 | impl CoreIntrinsic for Transmute { 123 | fn name(&self) -> &'static str { 124 | "transmute" 125 | } 126 | 127 | fn dest_type(&self, ty: TyId, tcx: &TyCtxt) -> bool { 128 | if ty.contains(tcx, |tcx, ty| match ty.kind(tcx) { 129 | // Tys with value validity contstraints 130 | TyKind::Unit | TyKind::Bool | TyKind::Char | TyKind::RawPtr(..) | TyKind::Ref(..) => { 131 | true 132 | } // TODO: pointer transmute 133 | _ => false, 134 | }) { 135 | return false; 136 | } 137 | if BasicMemory::ty_size(ty, tcx).is_none() { 138 | return false; 139 | } 140 | true 141 | } 142 | 143 | fn choose_operands(&self, ctx: &GenerationCtx, dest: &Place) -> Option> { 144 | let dest_size = BasicMemory::ty_size(dest.ty(ctx.current_decls(), &ctx.tcx), &ctx.tcx) 145 | .expect("dest must have known size"); 146 | // Avoid pointer to int casts 147 | let allowed_tys: Vec = ctx 148 | .tcx 149 | .indices() 150 | .filter(|ty| { 151 | !ty.contains(&ctx.tcx, |tcx, ty| { 152 | // Avoid inspecting the bytes in fp as NaN payload is nd 153 | ty.is_any_ptr(tcx) || ty == TyCtxt::F32 || ty == TyCtxt::F64 154 | }) 155 | }) 156 | .collect(); 157 | 158 | let (srcs, weights) = PlaceSelector::for_argument(ctx.tcx.clone()) 159 | .of_tys(&allowed_tys) 160 | .of_size(dest_size) 161 | .except(dest) 162 | .into_weighted(&ctx.pt)?; 163 | let src = ctx 164 | .make_choice_weighted(srcs.into_iter(), weights, |ppath| { 165 | Ok(ppath.to_place(&ctx.pt)) 166 | }) 167 | .ok()?; 168 | if src.ty(ctx.current_decls(), &ctx.tcx).is_copy(&ctx.tcx) { 169 | Some(vec![Operand::Copy(src)]) 170 | } else { 171 | Some(vec![Operand::Move(src)]) 172 | } 173 | } 174 | } 175 | 176 | impl GenerationCtx { 177 | pub fn choose_intrinsic(&self, dest: &Place) -> Result<(Callee, Vec)> { 178 | let choices: [Box; 4] = [ 179 | Box::new(Fmaf64), 180 | Box::new(ArithOffset), 181 | Box::new(Bswap), 182 | Box::new(Transmute), 183 | ]; 184 | 185 | let intrinsic = self.make_choice(choices.iter(), Result::Ok)?; 186 | intrinsic.generate_terminator(self, dest) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /abi/src/size.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright: The Rust Project contributors 3 | With modifications by Andy Wang 4 | 5 | Permission is hereby granted, free of charge, to any 6 | person obtaining a copy of this software and associated 7 | documentation files (the "Software"), to deal in the 8 | Software without restriction, including without 9 | limitation the rights to use, copy, modify, merge, 10 | publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software 12 | is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice 16 | shall be included in all copies or substantial portions 17 | of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 20 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 21 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 22 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 23 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 24 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 26 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 27 | DEALINGS IN THE SOFTWARE. 28 | */ 29 | use std::{ 30 | fmt, 31 | ops::{Add, AddAssign, Mul, Sub}, 32 | }; 33 | 34 | use super::align::Align; 35 | 36 | // Size of a type in bytes. 37 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 38 | pub struct Size { 39 | raw: u64, 40 | } 41 | 42 | // This is debug-printed a lot in larger structs, don't waste too much space there 43 | impl fmt::Debug for Size { 44 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 45 | write!(f, "Size({} bytes)", self.bytes()) 46 | } 47 | } 48 | 49 | impl Size { 50 | pub const ZERO: Size = Size { raw: 0 }; 51 | 52 | /// Rounds `bits` up to the next-higher byte boundary, if `bits` is 53 | /// not a multiple of 8. 54 | pub fn from_bits(bits: impl TryInto) -> Size { 55 | let bits = bits.try_into().ok().unwrap(); 56 | // Avoid potential overflow from `bits + 7`. 57 | Size { 58 | raw: bits / 8 + ((bits % 8) + 7) / 8, 59 | } 60 | } 61 | 62 | #[inline] 63 | pub fn from_bytes(bytes: impl TryInto) -> Size { 64 | let bytes: u64 = bytes.try_into().ok().unwrap(); 65 | Size { raw: bytes } 66 | } 67 | 68 | pub const fn from_bytes_const(bytes: u64) -> Size { 69 | Size { raw: bytes } 70 | } 71 | 72 | #[inline] 73 | pub fn bytes(self) -> u64 { 74 | self.raw 75 | } 76 | 77 | #[inline] 78 | pub fn bytes_usize(self) -> usize { 79 | self.bytes().try_into().unwrap() 80 | } 81 | 82 | #[inline] 83 | pub fn bits(self) -> u64 { 84 | #[cold] 85 | fn overflow(bytes: u64) -> ! { 86 | panic!("Size::bits: {} bytes in bits doesn't fit in u64", bytes) 87 | } 88 | 89 | self.bytes() 90 | .checked_mul(8) 91 | .unwrap_or_else(|| overflow(self.bytes())) 92 | } 93 | 94 | #[inline] 95 | pub fn bits_usize(self) -> usize { 96 | self.bits().try_into().unwrap() 97 | } 98 | 99 | #[inline] 100 | pub fn align_to(self, align: Align) -> Size { 101 | let mask = align.bytes() - 1; 102 | Size::from_bytes((self.bytes() + mask) & !mask) 103 | } 104 | 105 | #[inline] 106 | pub fn is_aligned(self, align: Align) -> bool { 107 | let mask = align.bytes() - 1; 108 | self.bytes() & mask == 0 109 | } 110 | 111 | /// Truncates `value` to `self` bits and then sign-extends it to 128 bits 112 | /// (i.e., if it is negative, fill with 1's on the left). 113 | #[inline] 114 | pub fn sign_extend(self, value: u128) -> u128 { 115 | let size = self.bits(); 116 | if size == 0 { 117 | // Truncated until nothing is left. 118 | return 0; 119 | } 120 | // Sign-extend it. 121 | let shift = 128 - size; 122 | // Shift the unsigned value to the left, then shift back to the right as signed 123 | // (essentially fills with sign bit on the left). 124 | (((value << shift) as i128) >> shift) as u128 125 | } 126 | 127 | /// Truncates `value` to `self` bits. 128 | #[inline] 129 | pub fn truncate(self, value: u128) -> u128 { 130 | let size = self.bits(); 131 | if size == 0 { 132 | // Truncated until nothing is left. 133 | return 0; 134 | } 135 | let shift = 128 - size; 136 | // Truncate (shift left to drop out leftover values, shift right to fill with zeroes). 137 | (value << shift) >> shift 138 | } 139 | 140 | #[inline] 141 | pub fn signed_int_min(&self) -> i128 { 142 | self.sign_extend(1_u128 << (self.bits() - 1)) as i128 143 | } 144 | 145 | #[inline] 146 | pub fn signed_int_max(&self) -> i128 { 147 | i128::MAX >> (128 - self.bits()) 148 | } 149 | 150 | #[inline] 151 | pub fn unsigned_int_max(&self) -> u128 { 152 | u128::MAX >> (128 - self.bits()) 153 | } 154 | } 155 | 156 | // Panicking addition, subtraction and multiplication for convenience. 157 | // Avoid during layout computation, return `LayoutError` instead. 158 | 159 | impl Add for Size { 160 | type Output = Size; 161 | #[inline] 162 | fn add(self, other: Size) -> Size { 163 | Size::from_bytes(self.bytes().checked_add(other.bytes()).unwrap_or_else(|| { 164 | panic!( 165 | "Size::add: {} + {} doesn't fit in u64", 166 | self.bytes(), 167 | other.bytes() 168 | ) 169 | })) 170 | } 171 | } 172 | 173 | impl Sub for Size { 174 | type Output = Size; 175 | #[inline] 176 | fn sub(self, other: Size) -> Size { 177 | Size::from_bytes(self.bytes().checked_sub(other.bytes()).unwrap_or_else(|| { 178 | panic!( 179 | "Size::sub: {} - {} would result in negative size", 180 | self.bytes(), 181 | other.bytes() 182 | ) 183 | })) 184 | } 185 | } 186 | 187 | impl Mul for u64 { 188 | type Output = Size; 189 | #[inline] 190 | fn mul(self, size: Size) -> Size { 191 | size * self 192 | } 193 | } 194 | 195 | impl Mul for Size { 196 | type Output = Size; 197 | #[inline] 198 | fn mul(self, count: u64) -> Size { 199 | match self.bytes().checked_mul(count) { 200 | Some(bytes) => Size::from_bytes(bytes), 201 | None => panic!("Size::mul: {} * {} doesn't fit in u64", self.bytes(), count), 202 | } 203 | } 204 | } 205 | 206 | impl AddAssign for Size { 207 | #[inline] 208 | fn add_assign(&mut self, other: Size) { 209 | *self = *self + other; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rustlantis 2 | A Rust Mid-level Intermediate Representation fuzzer 3 | 4 | It can generate [custom MIR](https://doc.rust-lang.org/std/intrinsics/mir/index.html) programs containing: 5 | - All primitive integer and floating point types, `bool`, `char`, arrays, 6 | tuples, references, raw pointers, structs, and enums. 7 | - Functions containing multiple basic blocks 8 | - Terminators: `Goto`, `Return`, `SwitchInt` (`match`), `Call`. 9 | - Intrinsic functions: `arith_offset` (for pointer arithmetics), `transmute`, 10 | `bswap`, `fmaf64`. 11 | - Operators: all arithmetic, logical and bitwise operations on integers 12 | and floating points, and checked arithmetic (Add, Sub, Mul) on integers 13 | - All primitive literal expressions, as well as tuple, array, and struct 14 | aggregate expressions 15 | - Creating references and raw pointers, and dereferencing them 16 | - Casts between integers, floating points, `char`, and `bool` 17 | 18 | Generated programs are terminating, UB-free, and deterministic. A discrepancy between testing backends 19 | always indicate a bug in them (or a bug in Rustlantis). 20 | 21 | ## Requirements 22 | - Rust nightly 23 | - rustup 24 | 25 | ## Config 26 | Install Miri and Cranelift with Rustup `rustup component add miri rustc-codegen-cranelift-preview`, then copy `config.toml.example` to `config.toml` 27 | 28 | ## Usage 29 | 30 | To generate and difftest one seed, run 31 | 32 | ```bash 33 | ./fuzz-one.sh 34 | ``` 35 | 36 | A program will be generated to `$TMPDIR` and tested. If difftest passes (no bug), it will exit with 0. If difftest spots a difference between testing backends, it will exit with 1 and save the reproduction file to `./repros/`. 37 | 38 | To generate a program only, run `generate` 39 | ``` 40 | Usage: generate [OPTIONS] 41 | 42 | Arguments: 43 | generation seed 44 | 45 | Options: 46 | -d, --debug generate a program where values are printed instead of hashed (slow) 47 | --call-syntax switch between different versions of Call syntaxes [default: v4] [possible values: v1, v2, v3, v4] 48 | -h, --help Print help 49 | -V, --version Print version 50 | ``` 51 | 52 | To difftest an existing program, run `difftest` 53 | ``` 54 | Usage: difftest 55 | 56 | Arguments: 57 | 58 | 59 | Options: 60 | -h, --help Print help 61 | ``` 62 | 63 | ## Quirks 64 | - Cranelift not supported on AArch64 macOS: https://github.com/bjorn3/rustc_codegen_cranelift/issues/1248 65 | - `rustc_codegen_gcc` can be used as a backend, but it doesn't support enough language features yet to be usable 66 | 67 | ## Namesake 68 | The Space Shuttle *Atlantis* docked with *Mir* space station seven times: https://en.wikipedia.org/wiki/Shuttle%E2%80%93Mir_program 69 | 70 | ## Trophies 71 | 72 | 🦀: Root cause in Rust 73 | 🐉: Root cause in LLVM 74 | 🏗️: Root cause in Cranelift 75 | 76 | ### Crashes & ICEs 77 | - 🦀 `RenameReturnPlace` is broken: https://github.com/rust-lang/rust/issues/110902 78 | - 🦀 `ReferencePropagation` prevents partial initialisation: https://github.com/rust-lang/rust/issues/111426 79 | - 🐉 phi nodes assumed to be non-empty: https://github.com/llvm/llvm-project/issues/63013 80 | - 🐉 Assertion failure in `RegisterCoalescer`: https://github.com/llvm/llvm-project/issues/63033 81 | - 🦀 MIR inlining inserts statements at the wrong place: https://github.com/rust-lang/rust/issues/117355 82 | - 🏗️ Overflowing shift triggers panic in Cranelift: https://github.com/rust-lang/rustc_codegen_cranelift/issues/1455 & https://github.com/bytecodealliance/wasmtime/issues/7865 83 | 84 | ### Silent Miscompilations 85 | - 🦀 `ConstProp` propagates over mutating borrows: https://github.com/rust-lang/rust/issues/110947 86 | - 🦀 `*const T` in function parameters annotated with `readonly`: https://github.com/rust-lang/rust/issues/111502 87 | - 🐉 Aliasing analysis merges loads from different offsets: https://github.com/rust-lang/rust/issues/112061 & https://github.com/llvm/llvm-project/issues/63019 88 | - 🐉 Constant folding produces invalid boolean values: https://github.com/rust-lang/rust/issues/112170 & https://github.com/llvm/llvm-project/issues/63055 89 | - 🐉 Aliasing analysis broken for overflowing pointer offsets: https://github.com/rust-lang/rust/issues/112526 & https://github.com/llvm/llvm-project/issues/63266 90 | - https://github.com/rust-lang/rust/issues/112548 91 | - 🐉 Copy elision corrupts stack arguments with two parts: https://github.com/rust-lang/rust/issues/112767 & https://github.com/llvm/llvm-project/issues/63430 92 | - 🐉 Copy elision reads stack arguments from the wrong offsets: https://github.com/llvm/llvm-project/issues/63475 93 | - 🦀 Subnormal f64 to f32 cast is wrong: https://github.com/rust-lang/rust/issues/113407 94 | - 🐉 AST size merging is wrong: https://github.com/llvm/llvm-project/issues/64897 95 | - 🦀 `ConstProp` propagates over assignment of unknown values: https://github.com/rust-lang/rust/issues/118328 96 | - 🐉 Bad `undef`/`poison` handling in `InstCombine`: https://github.com/llvm/llvm-project/issues/74890 97 | - 🦀 `GVN` merges moved function arguments: https://github.com/rust-lang/rust/issues/120613 98 | - 🐉 `GVNPass` forgets to remove poison generating flags: https://github.com/llvm/llvm-project/issues/82884 99 | - 🏗️ Misoptimization of imul + ireduce: https://github.com/rust-lang/rustc_codegen_cranelift/issues/1460 & https://github.com/bytecodealliance/wasmtime/issues/7999 100 | - 🐉 `InstCombine` calculates wrong `insertelement` instructions: https://github.com/rust-lang/rust/issues/121996 & https://github.com/llvm/llvm-project/issues/84025 101 | - 🐉 Funnel shifts by a constant 0 are lowered wrong on AArch64: https://github.com/llvm/llvm-project/issues/139866 102 | - 🦀 `GVN` misunderstands aliasing, can create overlapping assignments: https://github.com/rust-lang/rust/issues/141038 103 | - 🦀 `ReferencePropagation` misunderstands aliasing, can add reads of uninitialized memory: https://github.com/rust-lang/rust/issues/141101 104 | - 🦀 `CopyProp` doesn't always respect Tree Borrows aliasing: https://github.com/rust-lang/rust/issues/141122 105 | - 🦀 `EarlyOtherwiseBranch` can insert storage markers incorrectly, creating use of a dead local https://github.com/rust-lang/rust/issues/141212 106 | - 🦀 `GVN` makes an incorrect index access: https://github.com/rust-lang/rust/issues/141251 107 | - 🐉 `CorrelatedValuePropagation` miscompilation: https://github.com/llvm/llvm-project/issues/142286 108 | - 🐉 `InstCombine` miscompilation: https://github.com/llvm/llvm-project/issues/142518 109 | 110 | ### Previously known bugs 111 | - 🦀 Const eval gives `x % x` wrong sign when `x` is a negative float: https://github.com/rust-lang/rust/issues/109567 (first reported https://github.com/rust-lang/rust/issues/102403) 112 | - 🐉 Write to dangling pointer is hoisted outside of condition: https://github.com/rust-lang/rust/issues/112213 (first reported https://github.com/llvm/llvm-project/issues/51838) 113 | 114 | ## License 115 | Rustlantis is distributed under the terms of both the MIT License and the Apache License (Version 2.0), at your choice. 116 | 117 | © ETH Zurich and contributors 118 | -------------------------------------------------------------------------------- /generate/src/ty.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, fmt::Write, iter}; 2 | 3 | use index_vec::IndexVec; 4 | use log::{log_enabled, trace}; 5 | use mir::{ 6 | serialize::Serialize, 7 | syntax::{Adt, IntTy, Mutability, TyId, TyKind, VariantDef}, 8 | tyctxt::{AdtMeta, TyCtxt}, 9 | }; 10 | use rand::{ 11 | Rng, 12 | seq::{IteratorRandom, SliceRandom}, 13 | }; 14 | use rand_distr::{Distribution, Poisson, weighted::WeightedIndex}; 15 | 16 | #[derive(Clone)] 17 | pub struct TySelect { 18 | weights: WeightedIndex, 19 | } 20 | 21 | impl TySelect { 22 | pub fn new(tcx: &TyCtxt) -> Self { 23 | Self { 24 | weights: Self::distribute_weights(tcx), 25 | } 26 | } 27 | fn distribute_weights(tcx: &TyCtxt) -> WeightedIndex { 28 | let p_bool = 0.05; 29 | let p_char = 0.05; 30 | let p_floats = 0.1; 31 | let p_ints = 0.1; 32 | let p_isize = 0.1; 33 | let p_pointers = 0.3; 34 | 35 | // Types with special treatment as we want to increase their weighting 36 | let mut weights: HashMap = HashMap::new(); 37 | let mut p_sum: f32 = 0.; 38 | let num_ptrs = tcx 39 | .iter_enumerated() 40 | .filter(|(ty, _)| ty.is_any_ptr(tcx)) 41 | .count(); 42 | 43 | // All the types without special weighting 44 | let mut residual: Vec = Vec::new(); 45 | 46 | for (idx, ty) in tcx.iter_enumerated() { 47 | let p = match ty { 48 | TyKind::Unit => Some(0.), 49 | TyKind::Bool => Some(p_bool), 50 | TyKind::Char => Some(p_char), 51 | TyKind::Int(IntTy::Isize) => Some(p_isize), 52 | TyKind::Int(..) => Some(p_ints / TyKind::INTS.len() as f32), 53 | TyKind::Uint(..) => Some(p_ints / TyKind::INTS.len() as f32), 54 | TyKind::Float(..) => Some(p_floats / TyKind::FLOATS.len() as f32), 55 | TyKind::RawPtr(..) | TyKind::Ref(..) => Some(p_pointers / num_ptrs as f32), 56 | _ => None, 57 | }; 58 | if let Some(rate) = p { 59 | weights.insert(idx, rate); 60 | p_sum += rate; 61 | } else { 62 | residual.push(idx); 63 | } 64 | } 65 | assert!(p_sum < 1.); 66 | 67 | weights.extend( 68 | residual 69 | .iter() 70 | .map(|&tyid| (tyid, (1. - p_sum) / residual.len() as f32)), 71 | ); 72 | 73 | if log_enabled!(log::Level::Debug) { 74 | let mut s = String::new(); 75 | for (tyid, _) in tcx.iter_enumerated() { 76 | s.write_fmt(format_args!( 77 | "{}: {}\n", 78 | tyid.serialize(tcx), 79 | weights[&tyid] 80 | )) 81 | .unwrap(); 82 | } 83 | trace!("Typing context with weights:\n{s}"); 84 | trace!("{}", tcx.serialize()); 85 | } 86 | 87 | WeightedIndex::new(tcx.iter_enumerated().map(|(tyid, _)| weights[&tyid])) 88 | .expect("can produce weighted index") 89 | } 90 | 91 | pub fn choose_ty(&self, rng: &mut impl Rng, tcx: &TyCtxt) -> TyId { 92 | tcx.indices() 93 | .nth(self.weights.sample(rng)) 94 | .expect("tyctxt isn't empty") 95 | } 96 | } 97 | 98 | fn new_composite(tcx: &mut TyCtxt, rng: &mut impl Rng) { 99 | let new_ty = match rng.random_range(0..=3) { 100 | 0 => TyKind::Tuple({ 101 | let dist = Poisson::::new(2.7).unwrap(); 102 | let length = dist.sample(rng).clamp(1., tcx.config.tuple_max_len as f32) as usize; 103 | (0..length) 104 | .map(|_| { 105 | tcx.indices() 106 | .filter(|ty| *ty != TyCtxt::UNIT) 107 | .choose(rng) 108 | .unwrap() 109 | }) 110 | .collect() 111 | }), 112 | 1 => TyKind::RawPtr( 113 | tcx.indices() 114 | .filter(|ty| *ty != TyCtxt::UNIT) 115 | .choose(rng) 116 | .unwrap(), 117 | if rng.random_bool(0.5) { 118 | Mutability::Mut 119 | } else { 120 | Mutability::Not 121 | }, 122 | ), 123 | 2 => TyKind::Ref( 124 | tcx.indices() 125 | .filter(|ty| *ty != TyCtxt::UNIT) 126 | .choose(rng) 127 | .unwrap(), 128 | if rng.random_bool(0.5) { 129 | Mutability::Mut 130 | } else { 131 | Mutability::Not 132 | }, 133 | ), 134 | 3 => TyKind::Array( 135 | tcx.iter_enumerated() 136 | .filter_map(|(ty, kind)| (ty != TyCtxt::UNIT && kind.is_scalar()).then_some(ty)) 137 | .choose(rng) 138 | .unwrap(), 139 | rng.random_range(1..=tcx.config.array_max_len), 140 | ), 141 | _ => unreachable!(), 142 | }; 143 | if !tcx.iter().any(|ty| *ty == new_ty) { 144 | tcx.push(new_ty); 145 | } 146 | } 147 | 148 | fn new_adt(tcx: &mut TyCtxt, rng: &mut impl Rng) { 149 | // TODO: recursive types 150 | let variant_count = rng.random_range(1..=tcx.config.adt_max_variants); 151 | 152 | let variants = (0..variant_count).map(|_| { 153 | let field_count = rng.random_range(1..=tcx.config.struct_max_fields); 154 | let field_tys = tcx 155 | .indices() 156 | .filter(|ty| *ty != TyCtxt::UNIT && /* https://github.com/rust-lang/rust/issues/119940 */ !ty.contains(&tcx, |tcx, ty| ty.is_ref(tcx))) 157 | .choose_multiple(rng, field_count); 158 | VariantDef { 159 | fields: IndexVec::from_iter(field_tys.into_iter()), 160 | } 161 | }); 162 | let adt = Adt { 163 | variants: IndexVec::from_iter(variants), 164 | }; 165 | 166 | let copy = if adt.copy_derivable(&tcx) { 167 | rng.random_bool(0.5) 168 | } else { 169 | false 170 | }; 171 | 172 | let meta = AdtMeta { copy }; 173 | 174 | tcx.push_adt(adt, meta); 175 | } 176 | 177 | pub fn seed_tys(tcx: &mut TyCtxt, rng: &mut R) { 178 | #[derive(Clone, Copy)] 179 | enum Kind { 180 | Adt, 181 | Structural, 182 | } 183 | 184 | let mut choices: Vec = iter::repeat(Kind::Structural) 185 | .take(tcx.config.composite_count) 186 | .chain(iter::repeat(Kind::Adt).take(tcx.config.adt_count)) 187 | .collect(); 188 | choices.shuffle(rng); 189 | for choice in choices { 190 | match choice { 191 | Kind::Adt => new_adt(tcx, rng), 192 | Kind::Structural => new_composite(tcx, rng), 193 | } 194 | } 195 | } 196 | 197 | #[cfg(test)] 198 | mod tests { 199 | use std::collections::HashSet; 200 | 201 | use config::TyConfig; 202 | use mir::{syntax::TyId, tyctxt::TyCtxt}; 203 | use rand::SeedableRng; 204 | 205 | use crate::ty::seed_tys; 206 | 207 | #[test] 208 | fn tys_unique() { 209 | let mut rng = rand::rngs::SmallRng::seed_from_u64(0); 210 | let mut tcx = TyCtxt::from_primitives(TyConfig::default()); 211 | seed_tys(&mut tcx, &mut rng); 212 | let set: HashSet = tcx.indices().collect(); 213 | assert!(set.len() == tcx.len()) 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /rangemap/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /rangemap/src/lib.rs: -------------------------------------------------------------------------------- 1 | // From https://github.com/rust-lang/miri/blob/c00b33f37635de24fbfe24ac786c3967ea45c679/src/range_map.rs 2 | // Modified by Andy Wang 3 | 4 | //! Implements a map from integer indices to data. 5 | //! Rather than storing data for every index, internally, this maps entire ranges to the data. 6 | //! To this end, the APIs all work on ranges, not on individual integers. Ranges are split as 7 | //! necessary (e.g., when [0,5) is first associated with X, and then [1,2) is mutated). 8 | //! Users must not depend on whether a range is coalesced or not, even though this is observable 9 | //! via the iteration APIs. 10 | 11 | use std::ops; 12 | 13 | use abi::size::Size; 14 | 15 | #[derive(Clone, Debug)] 16 | struct Elem { 17 | /// The range covered by this element; never empty. 18 | range: ops::Range, 19 | /// The data stored for this element. 20 | data: T, 21 | } 22 | #[derive(Clone, Debug)] 23 | pub struct RangeMap { 24 | v: Vec>, 25 | } 26 | 27 | impl RangeMap { 28 | /// Creates a new `RangeMap` for the given size, and with the given initial value used for 29 | /// the entire range. 30 | #[inline(always)] 31 | pub fn new(size: Size, init: T) -> RangeMap { 32 | let size = size.bytes(); 33 | let mut map = RangeMap { v: Vec::new() }; 34 | if size > 0 { 35 | map.v.push(Elem { 36 | range: 0..size, 37 | data: init, 38 | }); 39 | } 40 | map 41 | } 42 | 43 | /// Finds the index containing the given offset. 44 | fn find_offset(&self, offset: u64) -> usize { 45 | // We do a binary search. 46 | let mut left = 0usize; // inclusive 47 | let mut right = self.v.len(); // exclusive 48 | loop { 49 | debug_assert!( 50 | left < right, 51 | "find_offset: offset {offset} is out-of-bounds" 52 | ); 53 | let candidate = left.checked_add(right).unwrap() / 2; 54 | let elem = &self.v[candidate]; 55 | if offset < elem.range.start { 56 | // We are too far right (offset is further left). 57 | debug_assert!(candidate < right); // we are making progress 58 | right = candidate; 59 | } else if offset >= elem.range.end { 60 | // We are too far left (offset is further right). 61 | debug_assert!(candidate >= left); // we are making progress 62 | left = candidate + 1; 63 | } else { 64 | // This is it! 65 | return candidate; 66 | } 67 | } 68 | } 69 | 70 | /// Provides read-only iteration over everything in the given range. This does 71 | /// *not* split items if they overlap with the edges. Do not use this to mutate 72 | /// through interior mutability. 73 | /// 74 | /// The iterator also provides the offset of the given element. 75 | pub fn iter(&self, offset: Size, len: Size) -> impl Iterator { 76 | let offset = offset.bytes(); 77 | let len = len.bytes(); 78 | // Compute a slice starting with the elements we care about. 79 | let slice: &[Elem] = if len == 0 { 80 | // We just need any empty iterator. We don't even want to 81 | // yield the element that surrounds this position. 82 | &[] 83 | } else { 84 | let first_idx = self.find_offset(offset); 85 | &self.v[first_idx..] 86 | }; 87 | // The first offset that is not included any more. 88 | let end = offset + len; 89 | assert!( 90 | end <= self.v.last().unwrap().range.end, 91 | "iterating beyond the bounds of this RangeMap" 92 | ); 93 | slice 94 | .iter() 95 | .take_while(move |elem| elem.range.start < end) 96 | .map(|elem| (Size::from_bytes(elem.range.start), &elem.data)) 97 | } 98 | 99 | pub fn iter_mut_all(&mut self) -> impl Iterator { 100 | self.v.iter_mut().map(|elem| &mut elem.data) 101 | } 102 | 103 | pub fn iter_all(&self) -> impl Iterator, &T)> { 104 | self.v.iter().map(|elem| (elem.range.clone(), &elem.data)) 105 | } 106 | 107 | // Splits the element situated at the given `index`, such that the 2nd one starts at offset 108 | // `split_offset`. Do nothing if the element already starts there. 109 | // Returns whether a split was necessary. 110 | fn split_index(&mut self, index: usize, split_offset: u64) -> bool 111 | where 112 | T: Clone, 113 | { 114 | let elem = &mut self.v[index]; 115 | if split_offset == elem.range.start || split_offset == elem.range.end { 116 | // Nothing to do. 117 | return false; 118 | } 119 | debug_assert!( 120 | elem.range.contains(&split_offset), 121 | "the `split_offset` is not in the element to be split" 122 | ); 123 | 124 | // Now we really have to split. Reduce length of first element. 125 | let second_range = split_offset..elem.range.end; 126 | elem.range.end = split_offset; 127 | // Copy the data, and insert second element. 128 | let second = Elem { 129 | range: second_range, 130 | data: elem.data.clone(), 131 | }; 132 | self.v.insert(index + 1, second); 133 | true 134 | } 135 | 136 | /// Provides mutable iteration over everything in the given range. As a side-effect, 137 | /// this will split entries in the map that are only partially hit by the given range, 138 | /// to make sure that when they are mutated, the effect is constrained to the given range. 139 | /// Moreover, this will opportunistically merge neighbouring equal blocks. 140 | /// 141 | /// The iterator also provides the offset of the given element. 142 | pub fn iter_mut(&mut self, offset: Size, len: Size) -> impl Iterator 143 | where 144 | T: Clone + PartialEq, 145 | { 146 | let offset = offset.bytes(); 147 | let len = len.bytes(); 148 | // Compute a slice containing exactly the elements we care about 149 | let slice: &mut [Elem] = if len == 0 { 150 | // We just need any empty iterator. We don't even want to 151 | // yield the element that surrounds this position, nor do 152 | // any splitting. 153 | &mut [] 154 | } else { 155 | // Make sure we got a clear beginning 156 | let mut first_idx = self.find_offset(offset); 157 | if self.split_index(first_idx, offset) { 158 | // The newly created 2nd element is ours 159 | first_idx += 1; 160 | } 161 | // No more mutation. 162 | let first_idx = first_idx; 163 | // Find our end. Linear scan, but that's ok because the iteration 164 | // is doing the same linear scan anyway -- no increase in complexity. 165 | // We combine this scan with a scan for duplicates that we can merge, to reduce 166 | // the number of elements. 167 | // We stop searching after the first "block" of size 1, to avoid spending excessive 168 | // amounts of time on the merging. 169 | let mut equal_since_idx = first_idx; 170 | // Once we see too many non-mergeable blocks, we stop. 171 | // The initial value is chosen via... magic. Benchmarking and magic. 172 | let mut successful_merge_count = 3usize; 173 | // When the loop is done, this is the first excluded element. 174 | let mut end_idx = first_idx; 175 | loop { 176 | // Compute if `end` is the last element we need to look at. 177 | let done = self.v[end_idx].range.end >= offset + len; 178 | // We definitely need to include `end`, so move the index. 179 | end_idx += 1; 180 | debug_assert!( 181 | done || end_idx < self.v.len(), 182 | "iter_mut: end-offset {} is out-of-bounds", 183 | offset + len 184 | ); 185 | // see if we want to merge everything in `equal_since..end` (exclusive at the end!) 186 | if successful_merge_count > 0 { 187 | if done || self.v[end_idx].data != self.v[equal_since_idx].data { 188 | // Everything in `equal_since..end` was equal. Make them just one element covering 189 | // the entire range. 190 | let removed_elems = end_idx - equal_since_idx - 1; // number of elements that we would remove 191 | if removed_elems > 0 { 192 | // Adjust the range of the first element to cover all of them. 193 | let equal_until = self.v[end_idx - 1].range.end; // end of range of last of the equal elements 194 | self.v[equal_since_idx].range.end = equal_until; 195 | // Delete the rest of them. 196 | self.v 197 | .splice(equal_since_idx + 1..end_idx, std::iter::empty()); 198 | // Adjust `end_idx` because we made the list shorter. 199 | end_idx -= removed_elems; 200 | // Adjust the count for the cutoff. 201 | successful_merge_count += removed_elems; 202 | } else { 203 | // Adjust the count for the cutoff. 204 | successful_merge_count -= 1; 205 | } 206 | // Go on scanning for the next block starting here. 207 | equal_since_idx = end_idx; 208 | } 209 | } 210 | // Leave loop if this is the last element. 211 | if done { 212 | break; 213 | } 214 | } 215 | // Move to last included instead of first excluded index. 216 | let end_idx = end_idx - 1; 217 | // We need to split the end as well. Even if this performs a 218 | // split, we don't have to adjust our index as we only care about 219 | // the first part of the split. 220 | self.split_index(end_idx, offset + len); 221 | // Now we yield the slice. `end` is inclusive. 222 | &mut self.v[first_idx..=end_idx] 223 | }; 224 | slice 225 | .iter_mut() 226 | .map(|elem| (Size::from_bytes(elem.range.start), &mut elem.data)) 227 | } 228 | } 229 | 230 | #[cfg(test)] 231 | mod tests { 232 | use super::*; 233 | 234 | /// Query the map at every offset in the range and collect the results. 235 | fn to_vec(map: &RangeMap, offset: u64, len: u64) -> Vec { 236 | (offset..offset + len) 237 | .map(|i| { 238 | map.iter(Size::from_bytes(i), Size::from_bytes(1)) 239 | .next() 240 | .map(|(_, &t)| t) 241 | .unwrap() 242 | }) 243 | .collect() 244 | } 245 | 246 | #[test] 247 | fn basic_insert() { 248 | let mut map = RangeMap::::new(Size::from_bytes(20), -1); 249 | // Insert. 250 | for (_, x) in map.iter_mut(Size::from_bytes(10), Size::from_bytes(1)) { 251 | *x = 42; 252 | } 253 | // Check. 254 | assert_eq!(to_vec(&map, 10, 1), vec![42]); 255 | assert_eq!(map.v.len(), 3); 256 | 257 | // Insert with size 0. 258 | for (_, x) in map.iter_mut(Size::from_bytes(10), Size::from_bytes(0)) { 259 | *x = 19; 260 | } 261 | for (_, x) in map.iter_mut(Size::from_bytes(11), Size::from_bytes(0)) { 262 | *x = 19; 263 | } 264 | assert_eq!(to_vec(&map, 10, 2), vec![42, -1]); 265 | assert_eq!(map.v.len(), 3); 266 | } 267 | 268 | #[test] 269 | fn gaps() { 270 | let mut map = RangeMap::::new(Size::from_bytes(20), -1); 271 | for (_, x) in map.iter_mut(Size::from_bytes(11), Size::from_bytes(1)) { 272 | *x = 42; 273 | } 274 | for (_, x) in map.iter_mut(Size::from_bytes(15), Size::from_bytes(1)) { 275 | *x = 43; 276 | } 277 | assert_eq!(map.v.len(), 5); 278 | assert_eq!( 279 | to_vec(&map, 10, 10), 280 | vec![-1, 42, -1, -1, -1, 43, -1, -1, -1, -1] 281 | ); 282 | 283 | for (_, x) in map.iter_mut(Size::from_bytes(10), Size::from_bytes(10)) { 284 | if *x < 42 { 285 | *x = 23; 286 | } 287 | } 288 | assert_eq!(map.v.len(), 6); 289 | assert_eq!( 290 | to_vec(&map, 10, 10), 291 | vec![23, 42, 23, 23, 23, 43, 23, 23, 23, 23] 292 | ); 293 | assert_eq!(to_vec(&map, 13, 5), vec![23, 23, 43, 23, 23]); 294 | 295 | for (_, x) in map.iter_mut(Size::from_bytes(15), Size::from_bytes(5)) { 296 | *x = 19; 297 | } 298 | assert_eq!(map.v.len(), 6); 299 | assert_eq!( 300 | to_vec(&map, 10, 10), 301 | vec![23, 42, 23, 23, 23, 19, 19, 19, 19, 19] 302 | ); 303 | // Should be seeing two blocks with 19. 304 | assert_eq!( 305 | map.iter(Size::from_bytes(15), Size::from_bytes(2)) 306 | .map(|(_, &t)| t) 307 | .collect::>(), 308 | vec![19, 19] 309 | ); 310 | 311 | // A NOP `iter_mut` should trigger merging. 312 | for _ in map.iter_mut(Size::from_bytes(15), Size::from_bytes(5)) {} 313 | assert_eq!(map.v.len(), 5); 314 | assert_eq!( 315 | to_vec(&map, 10, 10), 316 | vec![23, 42, 23, 23, 23, 19, 19, 19, 19, 19] 317 | ); 318 | } 319 | 320 | #[test] 321 | #[should_panic] 322 | fn out_of_range_iter_mut() { 323 | let mut map = RangeMap::::new(Size::from_bytes(20), -1); 324 | let _ = map.iter_mut(Size::from_bytes(11), Size::from_bytes(11)); 325 | } 326 | 327 | #[test] 328 | #[should_panic] 329 | fn out_of_range_iter() { 330 | let map = RangeMap::::new(Size::from_bytes(20), -1); 331 | let _ = map.iter(Size::from_bytes(11), Size::from_bytes(11)); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /generate/src/place_select.rs: -------------------------------------------------------------------------------- 1 | use std::{rc::Rc, vec}; 2 | 3 | use abi::size::Size; 4 | use mir::{ 5 | syntax::{Literal, Place, TyId}, 6 | tyctxt::TyCtxt, 7 | }; 8 | use rand_distr::weighted::WeightedIndex; 9 | 10 | use crate::{ 11 | mem::BasicMemory, 12 | pgraph::{PlaceGraph, PlaceIndex, PlacePath, ToPlaceIndex}, 13 | }; 14 | 15 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 16 | enum PlaceUsage { 17 | Operand, 18 | LHS, 19 | RET, 20 | SetDiscriminant, 21 | Pointee, 22 | Argument, 23 | KnownVal, 24 | NonZero, 25 | Offsetee, 26 | } 27 | 28 | #[derive(Clone)] 29 | pub struct PlaceSelector { 30 | tys: Option>, 31 | exclusions: Vec, 32 | moved: Vec, 33 | refed: Vec, 34 | size: Option, 35 | allow_uninit: bool, 36 | usage: PlaceUsage, 37 | tcx: Rc, 38 | } 39 | 40 | pub type Weight = usize; 41 | 42 | const RET_LHS_WEIGHT_FACTOR: Weight = 2; 43 | const UNINIT_WEIGHT_FACTOR: Weight = 2; 44 | const DEREF_WEIGHT_FACTOR: Weight = 20; 45 | const LIT_ARG_WEIGHT_FACTOR: Weight = 2; 46 | const PTR_ARG_WEIGHT_FACTOR: Weight = 20; 47 | const REF_ARG_WEIGHT_FACTOR: Weight = 20; 48 | const OFFSETTED_PTR_WEIGHT_FACTOR: Weight = 10; 49 | const ROUNDTRIPPED_PTR_WEIGHT_FACTOR: Weight = 100; 50 | 51 | impl PlaceSelector { 52 | pub fn for_pointee(tcx: Rc, allow_uninit: bool) -> Self { 53 | Self { 54 | usage: PlaceUsage::Pointee, 55 | allow_uninit, 56 | ..Self::for_operand(tcx) 57 | } 58 | } 59 | 60 | pub fn for_operand(tcx: Rc) -> Self { 61 | Self { 62 | tys: None, 63 | size: None, 64 | usage: PlaceUsage::Operand, 65 | exclusions: vec![], 66 | allow_uninit: false, 67 | tcx, 68 | moved: vec![], 69 | refed: vec![], 70 | } 71 | } 72 | 73 | pub fn for_set_discriminant(tcx: Rc) -> Self { 74 | Self { 75 | usage: PlaceUsage::SetDiscriminant, 76 | ..Self::for_operand(tcx) 77 | } 78 | } 79 | 80 | pub fn for_return_place(tcx: Rc) -> Self { 81 | Self { 82 | usage: PlaceUsage::RET, 83 | ..Self::for_lhs(tcx) 84 | } 85 | } 86 | 87 | pub fn for_argument(tcx: Rc) -> Self { 88 | Self { 89 | usage: PlaceUsage::Argument, 90 | ..Self::for_operand(tcx) 91 | } 92 | } 93 | 94 | pub fn for_lhs(tcx: Rc) -> Self { 95 | Self { 96 | usage: PlaceUsage::LHS, 97 | allow_uninit: true, 98 | ..Self::for_operand(tcx) 99 | } 100 | } 101 | 102 | pub fn for_known_val(tcx: Rc) -> Self { 103 | Self { 104 | usage: PlaceUsage::KnownVal, 105 | ..Self::for_operand(tcx) 106 | } 107 | } 108 | 109 | pub fn for_non_zero(tcx: Rc) -> Self { 110 | Self { 111 | usage: PlaceUsage::NonZero, 112 | ..Self::for_operand(tcx) 113 | } 114 | } 115 | 116 | pub fn for_offsetee(tcx: Rc) -> Self { 117 | Self { 118 | usage: PlaceUsage::Offsetee, 119 | ..Self::for_operand(tcx) 120 | } 121 | } 122 | 123 | pub fn of_ty(self, ty: TyId) -> Self { 124 | let tys = Some(vec![ty]); 125 | Self { tys, ..self } 126 | } 127 | 128 | pub fn of_tys(self, types: &[TyId]) -> Self { 129 | let tys = Some(Vec::from(types)); 130 | Self { tys, ..self } 131 | } 132 | 133 | pub fn of_size(self, size: Size) -> Self { 134 | Self { 135 | size: Some(size), 136 | ..self 137 | } 138 | } 139 | 140 | pub fn except(self, exclude: &Place) -> Self { 141 | let mut exclusions = self.exclusions; 142 | // TODO: More granular place discrimination 143 | exclusions.push(exclude.clone()); 144 | Self { exclusions, ..self } 145 | } 146 | 147 | pub fn having_moved(self, place: PlaceIndex) -> Self { 148 | assert_eq!(self.usage, PlaceUsage::Argument); 149 | let mut moved = self.moved; 150 | moved.push(place.clone()); 151 | Self { moved, ..self } 152 | } 153 | 154 | pub fn having_refed(self, target: PlaceIndex) -> Self { 155 | assert_eq!(self.usage, PlaceUsage::Argument); 156 | let mut refed = self.refed; 157 | refed.push(target.clone()); 158 | Self { refed, ..self } 159 | } 160 | 161 | fn into_iter_path(self, pt: &PlaceGraph) -> impl Iterator + Clone + '_ { 162 | let exclusion_indicies: Vec = self 163 | .exclusions 164 | .iter() 165 | .map(|place| place.to_place_index(pt).expect("excluded place exists")) 166 | .chain(pt.return_dest_stack()) // Don't touch anything that overlaps with any RET in the stack 167 | .chain(pt.moved_in_args_stack()) // Don't touch anything that overlaps with moved in args in the stack 168 | .collect(); 169 | let moved: Vec = self 170 | .moved 171 | .iter() 172 | .map(|place| place.to_place_index(pt).expect("place exists")) 173 | .collect(); 174 | let refed: Vec = self 175 | .refed 176 | .iter() 177 | .map(|place| place.to_place_index(pt).expect("place exists")) 178 | .collect(); 179 | pt.reachable_nodes().filter(move |ppath| { 180 | let index = ppath.target_index(); 181 | 182 | // Well-typedness 183 | if let Some(tys) = &self.tys 184 | && !tys.contains(&pt.ty(index)) 185 | { 186 | return false; 187 | } 188 | 189 | // Liveness 190 | if !pt.is_place_live(index) { 191 | return false; 192 | } 193 | 194 | // Initness 195 | if !self.allow_uninit && !pt.is_place_init(index) { 196 | return false; 197 | }; 198 | 199 | // Ref validity 200 | if self.usage != PlaceUsage::LHS && !pt.contains_only_valid_ref(index) { 201 | return false; 202 | } 203 | 204 | // Known val 205 | if self.usage == PlaceUsage::KnownVal && pt.known_val(index).is_none() { 206 | return false; 207 | } 208 | 209 | if self.usage == PlaceUsage::NonZero { 210 | let Some(known_val) = pt.known_val(index) else { 211 | return false; 212 | }; 213 | match known_val { 214 | Literal::Uint(v, _) if *v != 0 => {} 215 | Literal::Int(v, _) if *v != 0 => {} 216 | Literal::Float(v, _) if *v != 0. => {} 217 | _ => return false, 218 | } 219 | } 220 | 221 | // Avoid having ref in return type 222 | if self.usage == PlaceUsage::RET 223 | && pt.ty(index).contains(&self.tcx, |tcx, ty| ty.is_ref(tcx)) 224 | { 225 | return false; 226 | } 227 | 228 | // Not excluded 229 | if exclusion_indicies 230 | .iter() 231 | .any(|excl| pt.overlap(index, excl)) 232 | { 233 | return false; 234 | } 235 | 236 | // Has the right size 237 | if self.size.is_some() && BasicMemory::ty_size(pt.ty(index), &self.tcx) != self.size { 238 | return false; 239 | } 240 | 241 | // Check for aliasing rules 242 | match self.usage { 243 | // writes 244 | PlaceUsage::LHS | PlaceUsage::SetDiscriminant | PlaceUsage::RET => { 245 | if !pt.can_write_through(ppath.source(), index) { 246 | return false; 247 | } 248 | } 249 | // reads that will be done as moves 250 | PlaceUsage::Operand | PlaceUsage::Argument if !pt.ty(index).is_copy(&self.tcx) => { 251 | if !pt.can_write_through(ppath.source(), index) { 252 | return false; 253 | } 254 | } 255 | // reads 256 | _ => { 257 | if !pt.can_read_through(ppath.source(), index) { 258 | return false; 259 | } 260 | } 261 | } 262 | 263 | // Function arguments 264 | if self.usage == PlaceUsage::Argument { 265 | if moved.iter().any(|excl| pt.overlap(index, excl)) { 266 | return false; 267 | } 268 | // If this contains a ref, then it cannot point to an already-picked moved arg 269 | for m in &moved { 270 | if pt.contains_ref_to(index, *m) { 271 | return false; 272 | } 273 | } 274 | 275 | if !pt.ty(index).is_copy(&self.tcx) { 276 | // If this is a type that must be moved, then it must not be referenced by an already-picked reference 277 | for r in &refed { 278 | if pt.contains_ref_to(*r, index) { 279 | return false; 280 | } 281 | } 282 | } 283 | } 284 | 285 | true 286 | }) 287 | } 288 | 289 | pub fn into_weighted(self, pt: &PlaceGraph) -> Option<(Vec, WeightedIndex)> { 290 | let usage = self.usage; 291 | let tcx = self.tcx.clone(); 292 | let (places, weights): (Vec, Vec) = 293 | self.into_iter_path(pt) 294 | .map(|ppath| { 295 | let place = ppath.target_index(); 296 | let mut weight = match usage { 297 | PlaceUsage::Argument => { 298 | let mut weight = 1; 299 | let index = ppath.target_index(); 300 | let ty = pt.ty(index); 301 | if ty.contains(&tcx, |tcx, ty| ty.is_ref(tcx)) { 302 | weight *= REF_ARG_WEIGHT_FACTOR; 303 | } 304 | if ty.contains(&tcx, |tcx, ty| ty.is_raw_ptr(tcx)) { 305 | weight *= PTR_ARG_WEIGHT_FACTOR; 306 | } 307 | if pt.known_val(index).is_some() { 308 | weight *= LIT_ARG_WEIGHT_FACTOR; 309 | } 310 | // Encourage isize for pointer offset 311 | if ty.contains(&tcx, |_, ty| ty == TyCtxt::ISIZE) { 312 | weight *= LIT_ARG_WEIGHT_FACTOR; 313 | } 314 | if ty.is_raw_ptr(&tcx) && pt.offseted(index) { 315 | weight *= OFFSETTED_PTR_WEIGHT_FACTOR; 316 | } 317 | weight 318 | } 319 | PlaceUsage::LHS | PlaceUsage::SetDiscriminant | PlaceUsage::RET => { 320 | let mut weight = if !pt.is_place_init(place) { 321 | if ppath.is_return_proj(pt) { 322 | RET_LHS_WEIGHT_FACTOR 323 | } else { 324 | UNINIT_WEIGHT_FACTOR 325 | } 326 | } else { 327 | 1 328 | }; 329 | if pt.ty(place).is_raw_ptr(&tcx) && pt.get_offset(place).is_some() { 330 | weight = 0; 331 | } 332 | weight 333 | } 334 | PlaceUsage::Operand => pt.get_complexity(place), 335 | PlaceUsage::Pointee => 1, 336 | PlaceUsage::KnownVal | PlaceUsage::NonZero => pt.get_complexity(place), 337 | PlaceUsage::Offsetee => 1, 338 | }; 339 | 340 | if ppath.projections(pt).any(|proj| proj.is_deref()) { 341 | weight *= DEREF_WEIGHT_FACTOR; 342 | } 343 | 344 | if ppath.nodes(pt).any(|place| { 345 | pt.ty(place).is_raw_ptr(&tcx) && pt.has_offset_roundtripped(place) 346 | }) { 347 | weight *= ROUNDTRIPPED_PTR_WEIGHT_FACTOR; 348 | } 349 | 350 | (ppath, weight) 351 | }) 352 | .unzip(); 353 | if let Ok(weighted_index) = WeightedIndex::new(weights) { 354 | Some((places, weighted_index)) 355 | } else { 356 | None 357 | } 358 | } 359 | 360 | pub fn into_iter_place(self, pt: &PlaceGraph) -> impl Iterator + Clone + '_ { 361 | self.into_iter_path(pt).map(|ppath| ppath.to_place(pt)) 362 | } 363 | } 364 | 365 | #[cfg(test)] 366 | mod tests { 367 | extern crate test; 368 | use std::rc::Rc; 369 | 370 | use config::TyConfig; 371 | use mir::{ 372 | syntax::{Local, Place}, 373 | tyctxt::TyCtxt, 374 | }; 375 | use rand::{ 376 | Rng, SeedableRng, 377 | rngs::SmallRng, 378 | seq::{IndexedRandom, IteratorRandom}, 379 | }; 380 | use test::Bencher; 381 | 382 | use crate::{ 383 | pgraph::PlaceGraph, 384 | ty::{TySelect, seed_tys}, 385 | }; 386 | 387 | use super::PlaceSelector; 388 | 389 | fn build_pt(rng: &mut impl Rng) -> (PlaceGraph, Rc) { 390 | let mut tcx = TyCtxt::from_primitives(TyConfig::default()); 391 | seed_tys(&mut tcx, rng); 392 | let tcx = Rc::new(tcx); 393 | let mut pt = PlaceGraph::new(tcx.clone()); 394 | let ty_weights = TySelect::new(&tcx); 395 | for i in 0..=32 { 396 | let pidx = pt.allocate_local(Local::new(i), ty_weights.choose_ty(rng, &tcx)); 397 | if i % 2 == 0 { 398 | pt.mark_place_init(pidx); 399 | } 400 | } 401 | (pt, tcx) 402 | } 403 | 404 | #[bench] 405 | fn bench_select(b: &mut Bencher) { 406 | let mut rng = SmallRng::seed_from_u64(0); 407 | let (pt, tcx) = build_pt(&mut rng); 408 | 409 | b.iter(|| { 410 | PlaceSelector::for_lhs(tcx.clone()) 411 | .except(&Place::RETURN_SLOT) 412 | .into_iter_place(&pt) 413 | .choose(&mut rng) 414 | .expect("places not empty"); 415 | }) 416 | } 417 | 418 | #[bench] 419 | fn bench_materialise_into_vec(b: &mut Bencher) { 420 | let mut rng = SmallRng::seed_from_u64(0); 421 | let (pt, tcx) = build_pt(&mut rng); 422 | 423 | b.iter(|| { 424 | let places: Vec = PlaceSelector::for_lhs(tcx.clone()) 425 | .except(&Place::RETURN_SLOT) 426 | .into_iter_place(&pt) 427 | .collect(); 428 | 429 | // places.choose(&mut rng).expect("not empty"); 430 | places 431 | .choose_weighted(&mut rng, |p| p.projection().len()) 432 | .expect("places not empty"); 433 | }) 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /difftest/src/backends.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | env, 4 | ffi::{OsStr, OsString}, 5 | hash::{Hash, Hasher}, 6 | io::Write, 7 | path::{Path, PathBuf}, 8 | process::{self, Command, ExitStatus, Stdio}, 9 | }; 10 | 11 | use log::debug; 12 | 13 | use crate::Source; 14 | use config::BackendConfig; 15 | use config::Config; 16 | 17 | trait ClearEnv { 18 | fn clear_env(&mut self, preserve: &[&str]) -> &mut Command; 19 | } 20 | 21 | impl ClearEnv for Command { 22 | fn clear_env(&mut self, preserve: &[&str]) -> &mut Command { 23 | self.env_clear(); 24 | for env in preserve { 25 | if let Ok(existing) = env::var(env) { 26 | self.env(env, existing); 27 | } 28 | } 29 | self 30 | } 31 | } 32 | 33 | #[derive(Debug, PartialEq, Eq, Clone)] 34 | pub struct ProcessOutput { 35 | pub status: ExitStatus, 36 | /// The data that the process wrote to stdout. 37 | pub stdout: OsString, 38 | /// The data that the process wrote to stderr. 39 | pub stderr: OsString, 40 | } 41 | impl Hash for ProcessOutput { 42 | fn hash(&self, state: &mut H) { 43 | self.status.code().hash(state); 44 | self.stdout.hash(state); 45 | self.stderr.hash(state); 46 | } 47 | } 48 | 49 | impl From for ProcessOutput { 50 | fn from(value: process::Output) -> Self { 51 | let stdout: OsString; 52 | let stderr: OsString; 53 | #[cfg(unix)] 54 | { 55 | use std::os::unix::prelude::OsStrExt; 56 | stdout = OsStr::from_bytes(&value.stdout).to_owned(); 57 | stderr = OsStr::from_bytes(&value.stderr).to_owned(); 58 | } 59 | #[cfg(windows)] 60 | { 61 | use std::os::windows::prelude::OsStrExt; 62 | stdout = OsStr::from_wide(&value.stdout).to_owned(); 63 | stderr = OsStr::from_wide(&value.stderr).to_owned(); 64 | } 65 | Self { 66 | status: value.status, 67 | stdout, 68 | stderr, 69 | } 70 | } 71 | } 72 | 73 | pub fn from_config(config: Config) -> HashMap> { 74 | let mut backends = HashMap::new(); 75 | for (name, config) in config.backends { 76 | let backend: Box = match config { 77 | BackendConfig::Miri { toolchain, flags } => { 78 | Box::new(Miri::from_rustup(toolchain, flags).unwrap()) 79 | } 80 | BackendConfig::MiriRepo { path, flags } => { 81 | Box::new(Miri::from_repo(path, flags).unwrap()) 82 | } 83 | BackendConfig::LLVM { toolchain, flags } => Box::new(LLVM::new(toolchain, flags)), 84 | BackendConfig::Cranelift { toolchain, flags } => { 85 | Box::new(Cranelift::from_rustup(toolchain, flags)) 86 | } 87 | BackendConfig::CraneliftRepo { path, flags } => { 88 | Box::new(Cranelift::from_repo(path, flags).unwrap()) 89 | } 90 | BackendConfig::CraneliftBinary { path, flags } => { 91 | Box::new(Cranelift::from_binary(path, flags)) 92 | } 93 | BackendConfig::GCC { path, flags } => { 94 | Box::new(GCC::from_built_repo(path, flags).unwrap()) 95 | } 96 | }; 97 | backends.insert(name, backend); 98 | } 99 | backends 100 | } 101 | 102 | #[derive(Debug, PartialEq, Eq, Clone, Hash)] 103 | pub struct CompExecError(pub ProcessOutput); 104 | 105 | pub type ExecResult = Result; 106 | 107 | #[derive(Debug)] 108 | pub struct BackendInitError(pub String); 109 | 110 | pub trait Backend: Send + Sync { 111 | fn compile(&self, _: &Source, _: &Path) -> ProcessOutput { 112 | panic!("not implemented") 113 | } 114 | 115 | fn execute(&self, source: &Source, target: &Path) -> ExecResult { 116 | debug!("Compiling {source}"); 117 | let compile_out = self.compile(source, target); 118 | if !compile_out.status.success() { 119 | return Err(CompExecError(compile_out)); 120 | } 121 | 122 | debug!("Executing compiled {source}"); 123 | let exec_out = Command::new(target) 124 | .output() 125 | .expect("can execute target program and get output"); 126 | Ok(exec_out.into()) 127 | } 128 | } 129 | 130 | fn run_compile_command(mut command: Command, source: &Source) -> process::Output { 131 | let compiler = match source { 132 | Source::File(path) => { 133 | command.arg(path.canonicalize().expect("path is correct")); 134 | command 135 | .stdout(Stdio::piped()) 136 | .stderr(Stdio::piped()) 137 | .spawn() 138 | .expect("can spawn compiler") 139 | } 140 | Source::Stdin(code) => { 141 | command.arg("-").stdin(Stdio::piped()); 142 | let mut child = command 143 | .stdout(Stdio::piped()) 144 | .stderr(Stdio::piped()) 145 | .spawn() 146 | .expect("can spawn compiler"); 147 | child 148 | .stdin 149 | .as_mut() 150 | .unwrap() 151 | .write_all(code.as_bytes()) 152 | .unwrap(); 153 | child 154 | } 155 | }; 156 | 157 | let compile_out = compiler 158 | .wait_with_output() 159 | .expect("can execute rustc and get output"); 160 | 161 | compile_out 162 | } 163 | 164 | struct LLVM { 165 | toolchain: String, 166 | flags: Vec, 167 | } 168 | 169 | impl LLVM { 170 | fn new(toolchain: String, flags: Vec) -> Self { 171 | Self { toolchain, flags } 172 | } 173 | } 174 | 175 | impl Backend for LLVM { 176 | fn compile(&self, source: &Source, target: &Path) -> ProcessOutput { 177 | let mut command = Command::new("rustc"); 178 | 179 | command.arg(format!("+{}", self.toolchain)); 180 | 181 | command 182 | .args(["-o", target.to_str().unwrap()]) 183 | .args(["-C", "llvm-args=-protect-from-escaped-allocas=true"]) // https://github.com/rust-lang/rust/issues/112213 184 | .args(self.flags.clone()); 185 | 186 | run_compile_command(command, source).into() 187 | } 188 | } 189 | 190 | enum BackendSource { 191 | Path(PathBuf), 192 | Rustup(String), 193 | } 194 | 195 | struct Miri { 196 | miri: BackendSource, 197 | sysroot: PathBuf, 198 | flags: Vec, 199 | } 200 | 201 | impl Miri { 202 | fn find_sysroot(miri_source: &BackendSource) -> Result { 203 | let mut command = match miri_source { 204 | BackendSource::Path(source_dir) => { 205 | let mut cmd = Command::new(source_dir.join("target/release/cargo-miri")); 206 | cmd.current_dir(source_dir); 207 | cmd 208 | } 209 | BackendSource::Rustup(toolchain) => { 210 | let mut cmd = Command::new("rustup"); 211 | cmd.args(["run", toolchain, "cargo-miri"]); 212 | cmd 213 | } 214 | }; 215 | let output = command 216 | .arg("miri") 217 | .arg("setup") 218 | .arg("--print-sysroot") 219 | .clear_env(&["PATH", "DEVELOPER_DIR"]) 220 | .output() 221 | .expect("can run cargo-miri setup --print-sysroot"); 222 | if !output.status.success() { 223 | return Err(BackendInitError(format!( 224 | "failed to find sysroot: {output:?}" 225 | ))); 226 | } 227 | let sysroot; 228 | #[cfg(unix)] 229 | { 230 | use std::os::unix::prelude::OsStrExt; 231 | sysroot = OsStr::from_bytes(output.stdout.trim_ascii_end()).to_owned(); 232 | } 233 | #[cfg(windows)] 234 | { 235 | use std::os::windows::prelude::OsStrExt; 236 | sysroot = OsStr::from_wide(output.stdout.trim_ascii_end()).to_owned(); 237 | } 238 | 239 | let sysroot = PathBuf::from(sysroot); 240 | 241 | debug!("Miri sysroot at {}", sysroot.to_string_lossy()); 242 | if !Path::exists(&sysroot) { 243 | return Err(BackendInitError("sysroot does not exist".to_string())); 244 | } 245 | 246 | Ok(sysroot) 247 | } 248 | 249 | fn from_repo>( 250 | miri_dir: P, 251 | flags: Vec, 252 | ) -> Result { 253 | let miri_dir = miri_dir.as_ref(); 254 | 255 | // Detect if Miri already built 256 | if !Path::exists(&miri_dir.join("target/release/cargo-miri")) 257 | || !Path::exists(&miri_dir.join("target/release/miri")) 258 | { 259 | // Otherwise, build it ourselves 260 | debug!("Setting up miri toolchain"); 261 | let output = Command::new(miri_dir.join("miri")) 262 | .arg("toolchain") 263 | .output() 264 | .expect("can run miri toolchain and get output"); 265 | if !output.status.success() { 266 | return Err(BackendInitError(format!( 267 | "failed to set up Miri toolchain: {output:?}" 268 | ))); 269 | } 270 | 271 | debug!("Building Miri under {}", miri_dir.to_string_lossy()); 272 | let output = Command::new(miri_dir.join("miri")) 273 | .arg("build") 274 | .arg("--release") 275 | .clear_env(&["PATH", "DEVELOPER_DIR"]) 276 | .current_dir(miri_dir) 277 | .output() 278 | .expect("can run miri build and get output"); 279 | if !output.status.success() { 280 | return Err(BackendInitError(format!( 281 | "failed to build Miri: {output:?}" 282 | ))); 283 | } 284 | } else { 285 | debug!("Detected built Miri under {}", miri_dir.to_string_lossy()); 286 | } 287 | 288 | let sysroot = Self::find_sysroot(&BackendSource::Path(miri_dir.to_owned()))?; 289 | 290 | Ok(Self { 291 | miri: BackendSource::Path(miri_dir.join("target/release/miri")), 292 | sysroot, 293 | flags, 294 | }) 295 | } 296 | 297 | fn from_rustup(toolchain: String, flags: Vec) -> Result { 298 | let sysroot = Self::find_sysroot(&BackendSource::Rustup(toolchain.to_owned()))?; 299 | 300 | Ok(Self { 301 | miri: BackendSource::Rustup(toolchain), 302 | sysroot, 303 | flags, 304 | }) 305 | } 306 | } 307 | 308 | impl Backend for Miri { 309 | fn execute(&self, source: &Source, _: &Path) -> ExecResult { 310 | debug!("Executing with Miri {source}"); 311 | let mut command = match &self.miri { 312 | BackendSource::Path(binary) => Command::new(binary), 313 | BackendSource::Rustup(toolchain) => { 314 | let mut cmd = Command::new("rustup"); 315 | cmd.args(["run", &toolchain, "miri"]); 316 | cmd 317 | } 318 | }; 319 | command.args(self.flags.clone()); 320 | 321 | command 322 | .clear_env(&["PATH", "DEVELOPER_DIR"]) 323 | .args([OsStr::new("--sysroot"), self.sysroot.as_os_str()]); 324 | 325 | let miri_out = run_compile_command(command, source); 326 | 327 | // FIXME: we assume the source always exits with 0, and any non-zero return code 328 | // came from Miri itself (e.g. UB and type check errors) 329 | if !miri_out.status.success() { 330 | return Err(CompExecError(miri_out.into())); 331 | } 332 | Ok(miri_out.into()) 333 | } 334 | } 335 | 336 | struct Cranelift { 337 | clif: BackendSource, 338 | flags: Vec, 339 | } 340 | 341 | impl Cranelift { 342 | fn from_repo>( 343 | clif_dir: P, 344 | flags: Vec, 345 | ) -> Result { 346 | let clif_dir = clif_dir.as_ref(); 347 | 348 | if !Path::exists(&clif_dir.join("dist/rustc-clif")) { 349 | debug!("Setting up cranelift under {}", clif_dir.to_string_lossy()); 350 | let output = Command::new(clif_dir.join("y.rs")) 351 | .arg("prepare") 352 | .clear_env(&["PATH", "DEVELOPER_DIR"]) 353 | .current_dir(clif_dir) 354 | .output() 355 | .expect("can run y.rs prepare and get output"); 356 | if !output.status.success() { 357 | return Err(BackendInitError(format!( 358 | "failed to prepare Cranelift: {output:?}" 359 | ))); 360 | } 361 | 362 | let output = Command::new(clif_dir.join("y.rs")) 363 | .arg("build") 364 | .clear_env(&["PATH", "DEVELOPER_DIR"]) 365 | .current_dir(clif_dir) 366 | .output() 367 | .expect("can run y.rs build and get output"); 368 | if !output.status.success() { 369 | return Err(BackendInitError(format!( 370 | "failed to build Cranelift: {output:?}" 371 | ))); 372 | } 373 | } else { 374 | debug!("Found built Cranelift under {}", clif_dir.to_string_lossy()); 375 | } 376 | 377 | Ok(Cranelift { 378 | clif: BackendSource::Path(clif_dir.join("dist/rustc-clif")), 379 | flags, 380 | }) 381 | } 382 | 383 | fn from_binary>(binary_path: P, flags: Vec) -> Self { 384 | Self { 385 | clif: BackendSource::Path(binary_path.as_ref().to_owned()), 386 | flags, 387 | } 388 | } 389 | 390 | fn from_rustup(toolchain: String, flags: Vec) -> Self { 391 | Self { 392 | clif: BackendSource::Rustup(toolchain), 393 | flags, 394 | } 395 | } 396 | } 397 | 398 | impl Backend for Cranelift { 399 | fn compile(&self, source: &Source, target: &Path) -> ProcessOutput { 400 | let mut command = match &self.clif { 401 | BackendSource::Path(binary) => Command::new(binary), 402 | BackendSource::Rustup(toolchain) => { 403 | let mut cmd = Command::new("rustc"); 404 | cmd.arg(format!("+{toolchain}")); 405 | cmd.args(["-Z", "codegen-backend=cranelift"]); 406 | cmd 407 | } 408 | }; 409 | command 410 | .args(["-o", target.to_str().unwrap()]) 411 | .args(self.flags.clone()); 412 | run_compile_command(command, source).into() 413 | } 414 | } 415 | 416 | struct GCC { 417 | library: PathBuf, 418 | sysroot: PathBuf, 419 | repo: PathBuf, 420 | flags: Vec, 421 | } 422 | 423 | impl GCC { 424 | fn from_built_repo>( 425 | cg_gcc: P, 426 | flags: Vec, 427 | ) -> Result { 428 | let Ok(cg_gcc) = cg_gcc.as_ref().to_owned().canonicalize() else { 429 | return Err(BackendInitError( 430 | "cannot rustc_codegen_gcc repo".to_string(), 431 | )); 432 | }; 433 | 434 | let Ok(library) = cg_gcc 435 | .join("target/release/librustc_codegen_gcc.so") 436 | .canonicalize() 437 | else { 438 | return Err(BackendInitError( 439 | "cannot find librustc_codegen_gcc.so".to_string(), 440 | )); 441 | }; 442 | let Ok(sysroot) = cg_gcc.join("build_sysroot/sysroot").canonicalize() else { 443 | return Err(BackendInitError("cannot find sysroot".to_string())); 444 | }; 445 | 446 | Ok(Self { 447 | library, 448 | sysroot, 449 | repo: cg_gcc, 450 | flags, 451 | }) 452 | } 453 | } 454 | 455 | impl Backend for GCC { 456 | fn compile(&self, source: &Source, target: &Path) -> ProcessOutput { 457 | let mut command = Command::new("rustc"); 458 | command 459 | .clear_env(&["PATH", "DEVELOPER_DIR", "LD_LIBRARY_PATH"]) 460 | .current_dir(&self.repo) 461 | .args([ 462 | "-Z", 463 | &format!("codegen-backend={}", self.library.to_str().unwrap()), 464 | ]) 465 | .arg("--sysroot") 466 | .arg(&self.sysroot) 467 | .args(["-o", target.to_str().unwrap()]) 468 | .args(self.flags.clone()); 469 | run_compile_command(command, source).into() 470 | } 471 | } 472 | -------------------------------------------------------------------------------- /generate/src/mem/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{BTreeSet, HashMap}, 3 | fmt, mem, 4 | ops::Range, 5 | }; 6 | 7 | use abi::size::Size; 8 | use index_vec::{IndexVec, define_index_type}; 9 | use mir::{ 10 | syntax::{TyId, TyKind}, 11 | tyctxt::TyCtxt, 12 | }; 13 | use rangemap::RangeMap; 14 | use smallvec::SmallVec; 15 | 16 | define_index_type! {pub struct Tag = u32;} 17 | 18 | #[derive(Clone, Copy, PartialEq, Eq)] 19 | pub enum AbstractByte { 20 | /// An uninitialized byte. 21 | Uninit, 22 | /// An initialized byte, optionally with some provenance (if it is encoding a pointer). 23 | Init, 24 | } 25 | 26 | impl fmt::Debug for AbstractByte { 27 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 28 | match self { 29 | Self::Uninit => write!(f, "UU"), 30 | Self::Init => write!(f, "II"), 31 | } 32 | } 33 | } 34 | 35 | impl AbstractByte { 36 | pub fn is_init(&self) -> bool { 37 | self == &AbstractByte::Init 38 | } 39 | } 40 | 41 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 42 | pub enum BorrowType { 43 | Raw, 44 | Shared, 45 | Exclusive, 46 | } 47 | 48 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 49 | pub struct Borrow { 50 | borrow_type: BorrowType, 51 | tag: Tag, 52 | protected: bool, 53 | } 54 | 55 | /// A Run represents a contiguous region of memory free of padding 56 | #[derive(Debug, Clone)] 57 | pub struct Run { 58 | bytes: Box<[AbstractByte]>, 59 | ref_stack: RangeMap>, 60 | 61 | // A cache on if any borrow stack in the run has a mutable borrow 62 | _has_mut: Option, 63 | } 64 | 65 | impl Run { 66 | pub fn new_uninit(size: Size) -> Self { 67 | let bytes = vec![AbstractByte::Uninit; size.bytes() as usize].into_boxed_slice(); 68 | let ref_stack = RangeMap::new(size, vec![]); 69 | Self { 70 | bytes, 71 | ref_stack, 72 | _has_mut: Some(false), 73 | } 74 | } 75 | 76 | #[allow(dead_code)] 77 | pub fn size(&self) -> Size { 78 | Size::from_bytes(self.bytes.len()) 79 | } 80 | 81 | pub fn add_borrow(&mut self, offset: Size, len: Size, borrow_type: BorrowType, tag: Tag) { 82 | if borrow_type == BorrowType::Exclusive { 83 | self._has_mut = Some(true); 84 | } 85 | for (_, stack) in self.ref_stack.iter_mut(offset, len) { 86 | stack.push(Borrow { 87 | borrow_type, 88 | tag, 89 | protected: false, 90 | }); 91 | } 92 | } 93 | 94 | pub fn remove_borrow(&mut self, offset: Size, len: Size, tag: Tag) { 95 | self._has_mut = None; 96 | for (_, stack) in self.ref_stack.iter_mut(offset, len) { 97 | if let Some(i) = stack.iter().position(|b| b.tag == tag) { 98 | let removed = stack.remove(i); 99 | assert!(!removed.protected); 100 | } 101 | } 102 | } 103 | 104 | pub fn protect(&mut self, offset: Size, len: Size, tag: Tag) { 105 | for (_, stack) in self.ref_stack.iter_mut(offset, len) { 106 | if let Some(i) = stack.iter().position(|b| b.tag == tag) { 107 | stack[i].protected = true; 108 | } 109 | } 110 | } 111 | 112 | /// Returns a list of tags above a certain tag 113 | pub fn tags_above(&self, offset: Size, len: Size, tag: Tag) -> Vec { 114 | let mut edges = BTreeSet::new(); 115 | for (_, stack) in self.ref_stack.iter(offset, len) { 116 | let index = stack 117 | .iter() 118 | .position(|borrow| borrow.tag == tag) 119 | .expect("tag exists"); 120 | edges.extend(stack[index + 1..].iter().map(|borrow| borrow.tag)); 121 | } 122 | edges.iter().copied().collect() 123 | } 124 | 125 | pub fn above_first_ref(&self, offset: Size, len: Size) -> Vec { 126 | let mut edges = BTreeSet::new(); 127 | for (_, stack) in self.ref_stack.iter(offset, len) { 128 | let first_shared = stack.iter().position(|borrow| { 129 | matches!( 130 | borrow.borrow_type, 131 | BorrowType::Shared | BorrowType::Exclusive 132 | ) 133 | }); 134 | if let Some(first_shared) = first_shared { 135 | edges.extend(stack[first_shared..].iter().map(|borrow| borrow.tag)); 136 | } 137 | } 138 | edges.iter().copied().collect() 139 | } 140 | 141 | fn has_mut(&self, offset: Size, len: Size) -> bool { 142 | if let Some(has_mut) = self._has_mut { 143 | return has_mut; 144 | } 145 | for (_, stack) in self.ref_stack.iter(offset, len) { 146 | if stack 147 | .iter() 148 | .any(|borrow| borrow.borrow_type == BorrowType::Exclusive) 149 | { 150 | return true; 151 | } 152 | } 153 | false 154 | } 155 | 156 | /// Checks if a tag is below the last exclusive reference 157 | fn is_below_last_mut(&self, offset: Size, len: Size, tag: Tag) -> bool { 158 | for (_, stack) in self.ref_stack.iter(offset, len) { 159 | let last_mut = stack 160 | .iter() 161 | .rposition(|borrow| borrow.borrow_type == BorrowType::Exclusive); 162 | if let Some(pos) = last_mut { 163 | if stack[..pos] 164 | .iter() 165 | .find(|borrow| borrow.tag == tag) 166 | .is_some() 167 | { 168 | return true; 169 | } 170 | } 171 | } 172 | false 173 | } 174 | 175 | pub fn can_read_with(&self, offset: Size, len: Size, tag: Tag) -> bool { 176 | if self.is_below_last_mut(offset, len, tag) { 177 | return false; 178 | } 179 | self.ref_stack 180 | .iter(offset, len) 181 | .all(|(_, stack)| stack.iter().any(|borrow| borrow.tag == tag)) 182 | } 183 | 184 | pub fn can_write_with(&self, offset: Size, len: Size, tag: Tag) -> bool { 185 | if self.is_below_last_mut(offset, len, tag) { 186 | return false; 187 | } 188 | for (_, stack) in self.ref_stack.iter(offset, len) { 189 | let first_shared = stack 190 | .iter() 191 | .position(|borrow| borrow.borrow_type == BorrowType::Shared); 192 | let mut tag_search_limit = stack.len(); 193 | if let Some(first_shared) = first_shared { 194 | tag_search_limit = first_shared; 195 | // nothing above first shared (which will be popped off) 196 | // is protected 197 | if stack[first_shared..].iter().any(|borrow| borrow.protected) { 198 | return false; 199 | } 200 | } 201 | // and the tag exists below first shared (or anywhere, if there is no shared) 202 | if stack[..tag_search_limit] 203 | .iter() 204 | .find(|borrow| borrow.tag == tag) 205 | .is_none() 206 | { 207 | return false; 208 | } 209 | } 210 | true 211 | } 212 | } 213 | 214 | define_index_type! {pub struct RunId = u32;} 215 | 216 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 217 | pub struct RunAndOffset(RunId, Size); 218 | 219 | impl RunAndOffset { 220 | pub fn same_run(&self, other: &Self) -> bool { 221 | self.0 == other.0 222 | } 223 | 224 | pub fn offset(&self, offset: isize) -> Self { 225 | Self(self.0, Size::from_bytes(self.1.bytes() as isize + offset)) 226 | } 227 | } 228 | 229 | #[derive(Clone)] 230 | struct Allocation { 231 | /// The data stored in this allocation. 232 | runs: IndexVec, 233 | /// The alignment that was requested for this allocation. 234 | // align: Align, 235 | /// Whether this allocation is still live. 236 | live: bool, 237 | } 238 | 239 | #[allow(dead_code)] 240 | impl Allocation { 241 | fn runs_and_sizes(&self) -> impl Iterator + '_ { 242 | self.runs 243 | .iter_enumerated() 244 | .map(|(run_id, run)| (run_id, run.size())) 245 | } 246 | 247 | fn run(&self, run_and_offset: RunAndOffset) -> &Run { 248 | &self.runs[run_and_offset.0] 249 | } 250 | } 251 | 252 | pub struct AllocationBuilder { 253 | alloc_id: AllocId, 254 | runs: IndexVec, 255 | } 256 | 257 | impl AllocationBuilder { 258 | pub fn new_run(&mut self, size: Size) -> RunAndOffset { 259 | let run = Run::new_uninit(size); 260 | let run_id = self.runs.push(run); 261 | RunAndOffset(run_id, Size::ZERO) 262 | } 263 | 264 | pub fn alloc_id(&self) -> AllocId { 265 | self.alloc_id 266 | } 267 | 268 | fn build(self) -> Allocation { 269 | Allocation { 270 | runs: self.runs, 271 | live: true, 272 | } 273 | } 274 | } 275 | 276 | trait RangeExt: Sized { 277 | fn overlap(&self, other: &Self) -> bool; 278 | fn subtract(&self, other: &Self) -> [Option; 2]; 279 | } 280 | 281 | impl RangeExt for Range { 282 | fn overlap(&self, other: &Self) -> bool { 283 | self.start <= other.end && other.start <= self.end 284 | } 285 | fn subtract(&self, other: &Self) -> [Option; 2] { 286 | assert!(self.overlap(other)); 287 | let left = if self.start < other.start { 288 | Some(self.start..other.start) 289 | } else { 290 | None 291 | }; 292 | let right = if other.end < self.end { 293 | Some(other.end..self.end) 294 | } else { 295 | None 296 | }; 297 | [left, right] 298 | } 299 | } 300 | 301 | define_index_type! {pub struct AllocId = u32;} 302 | 303 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 304 | pub struct RunPointer { 305 | pub alloc_id: AllocId, 306 | pub run_and_offset: RunAndOffset, 307 | pub size: Size, 308 | } 309 | 310 | impl RunPointer { 311 | pub fn from_bytes_range(range: Range, alloc_id: AllocId, run: RunId) -> Self { 312 | RunPointer { 313 | alloc_id, 314 | run_and_offset: RunAndOffset(run, Size::from_bytes(range.start)), 315 | size: Size::from_bytes(range.count()), 316 | } 317 | } 318 | 319 | pub fn run(&self) -> RunId { 320 | self.run_and_offset.0 321 | } 322 | 323 | pub fn offset(&self) -> Size { 324 | self.run_and_offset.1 325 | } 326 | 327 | #[allow(dead_code)] 328 | pub fn len(&self) -> Size { 329 | self.size 330 | } 331 | 332 | pub fn bytes_range(&self) -> Range { 333 | self.run_and_offset.1.bytes_usize() 334 | ..self.run_and_offset.1.bytes_usize() + self.size.bytes_usize() 335 | } 336 | 337 | pub fn overlap(&self, other: &Self) -> bool { 338 | if self.alloc_id != other.alloc_id { 339 | return false; 340 | } 341 | if !self.run_and_offset.same_run(&other.run_and_offset) { 342 | return false; 343 | } 344 | self.bytes_range().overlap(&other.bytes_range()) 345 | } 346 | } 347 | 348 | #[derive(Clone)] 349 | pub struct BasicMemory { 350 | allocations: IndexVec, 351 | 352 | // a lookup table to aid removal from borrow stacks 353 | // a tag may cover multiple runs, e.g. &(u32, u32), 354 | pointers: HashMap>, 355 | } 356 | 357 | impl BasicMemory { 358 | const PTR_SIZE: Size = Size::from_bytes_const(mem::size_of::<*const ()>() as u64); 359 | 360 | pub fn new() -> Self { 361 | Self { 362 | allocations: IndexVec::new(), 363 | pointers: HashMap::new(), 364 | } 365 | } 366 | 367 | pub fn allocate_with_builder(&mut self, build: F) -> AllocId 368 | where 369 | F: FnOnce(&mut AllocationBuilder), 370 | { 371 | let alloc_id = self.allocations.len_idx(); 372 | let mut builder = AllocationBuilder { 373 | alloc_id, 374 | runs: IndexVec::new(), 375 | }; 376 | build(&mut builder); 377 | self.allocations.push(builder.build()) 378 | } 379 | 380 | pub fn deallocate(&mut self, alloc_id: AllocId) { 381 | self.allocations[alloc_id].live = false; 382 | } 383 | 384 | pub fn is_live(&self, alloc_id: AllocId) -> bool { 385 | self.allocations[alloc_id].live 386 | } 387 | 388 | pub fn bytes(&self, run_ptr: RunPointer) -> &[AbstractByte] { 389 | assert!( 390 | self.allocations[run_ptr.alloc_id].live, 391 | "can't access dead bytes" 392 | ); 393 | &self.allocations[run_ptr.alloc_id].runs[run_ptr.run()].bytes[run_ptr.bytes_range()] 394 | } 395 | 396 | pub fn fill(&mut self, run_ptr: RunPointer, val: AbstractByte) { 397 | self.bytes_mut(run_ptr).fill(val); 398 | } 399 | 400 | pub fn bytes_mut(&mut self, run_ptr: RunPointer) -> &mut [AbstractByte] { 401 | assert!( 402 | self.allocations[run_ptr.alloc_id].live, 403 | "can't access dead bytes" 404 | ); 405 | &mut self.allocations[run_ptr.alloc_id].runs[run_ptr.run()].bytes[run_ptr.bytes_range()] 406 | } 407 | 408 | pub fn copy(&mut self, dst: RunPointer, src: RunPointer) { 409 | assert_eq!(dst.size, src.size); 410 | let tmp = self.bytes(src).to_vec(); 411 | self.bytes_mut(dst).copy_from_slice(&tmp) 412 | } 413 | 414 | /// Returns Size for types with guaranteed size. 415 | /// Composite types under the default layout has no guaranteed size, 416 | /// as the AM is free to insert arbitarily large paddings. 417 | pub fn ty_size(ty: TyId, tcx: &TyCtxt) -> Option { 418 | Some(match ty { 419 | TyCtxt::UNIT => Size::ZERO, 420 | TyCtxt::BOOL => Size::from_bytes(1), 421 | TyCtxt::CHAR => Size::from_bytes(4), 422 | TyCtxt::I8 | TyCtxt::U8 => Size::from_bits(8), 423 | TyCtxt::I16 | TyCtxt::U16 => Size::from_bits(16), 424 | TyCtxt::I32 | TyCtxt::U32 => Size::from_bits(32), 425 | TyCtxt::I64 | TyCtxt::U64 => Size::from_bits(64), 426 | TyCtxt::I128 | TyCtxt::U128 => Size::from_bits(128), 427 | TyCtxt::F32 => Size::from_bits(32), 428 | TyCtxt::F64 => Size::from_bits(64), 429 | TyCtxt::ISIZE | TyCtxt::USIZE => Self::PTR_SIZE, 430 | _ => match ty.kind(tcx) { 431 | TyKind::RawPtr(..) => Self::PTR_SIZE, 432 | TyKind::Ref(..) => Self::PTR_SIZE, 433 | TyKind::Array(ty, len) => { 434 | return Self::ty_size(*ty, tcx) 435 | .map(|elem| Size::from_bytes(elem.bytes_usize() * len)); 436 | } 437 | _ => return None, 438 | }, 439 | }) 440 | } 441 | 442 | pub fn add_ref(&mut self, run_ptr: RunPointer, borrow_type: BorrowType, tag: Tag) { 443 | self.allocations[run_ptr.alloc_id].runs[run_ptr.run()].add_borrow( 444 | run_ptr.offset(), 445 | run_ptr.size, 446 | borrow_type, 447 | tag, 448 | ); 449 | self.pointers 450 | .entry(tag) 451 | .and_modify(|ptrs| ptrs.push(run_ptr)) 452 | .or_insert(SmallVec::from([run_ptr].as_slice())); 453 | } 454 | 455 | /// Remove a range (run_ptr) from the lookup table. 456 | fn derange(&mut self, tag: Tag, run_ptr: RunPointer) { 457 | if let Some(all_run_ptrs) = self.pointers.get(&tag) { 458 | // Check if the run_ptr we removed overlaps with ones cached, then remove/split them as necessary 459 | let mut updated = SmallVec::new(); 460 | for stored in all_run_ptrs { 461 | if stored.overlap(&run_ptr) { 462 | let left_and_right = stored.bytes_range().subtract(&run_ptr.bytes_range()); 463 | for range in left_and_right { 464 | if let Some(range) = range { 465 | updated.push(RunPointer::from_bytes_range( 466 | range, 467 | stored.alloc_id, 468 | stored.run(), 469 | )); 470 | } 471 | } 472 | } else { 473 | updated.push(*stored); 474 | } 475 | } 476 | 477 | let empty = updated.is_empty(); 478 | if empty { 479 | self.pointers.remove(&tag); 480 | } else { 481 | self.pointers.insert(tag, updated); 482 | } 483 | } 484 | } 485 | 486 | /// Remove tag for a run ptr. Returns true if the ref is no longer present in any 487 | /// borrow stack 488 | pub fn remove_tag_run_ptr(&mut self, tag: Tag, run_ptr: RunPointer) -> bool { 489 | self.allocations[run_ptr.alloc_id].runs[run_ptr.run()].remove_borrow( 490 | run_ptr.offset(), 491 | run_ptr.size, 492 | tag, 493 | ); 494 | 495 | self.derange(tag, run_ptr); 496 | !self.pointers.contains_key(&tag) 497 | } 498 | 499 | pub fn tags_above(&self, run_ptr: RunPointer, tag: Tag) -> Vec { 500 | self.allocations[run_ptr.alloc_id].runs[run_ptr.run()].tags_above( 501 | run_ptr.offset(), 502 | run_ptr.size, 503 | tag, 504 | ) 505 | } 506 | 507 | pub fn above_first_ref(&self, run_ptr: RunPointer) -> Vec { 508 | self.allocations[run_ptr.alloc_id].runs[run_ptr.run()] 509 | .above_first_ref(run_ptr.offset(), run_ptr.size) 510 | } 511 | 512 | pub fn mark_protected(&mut self, run_ptr: RunPointer, tag: Tag) { 513 | self.allocations[run_ptr.alloc_id].runs[run_ptr.run()].protect( 514 | run_ptr.offset(), 515 | run_ptr.size, 516 | tag, 517 | ) 518 | } 519 | 520 | pub fn can_read_with(&self, run_ptr: RunPointer, tag: Tag) -> bool { 521 | self.allocations[run_ptr.alloc_id].runs[run_ptr.run()].can_read_with( 522 | run_ptr.offset(), 523 | run_ptr.size, 524 | tag, 525 | ) 526 | } 527 | 528 | pub fn can_write_with(&self, run_ptr: RunPointer, tag: Tag) -> bool { 529 | self.allocations[run_ptr.alloc_id].runs[run_ptr.run()].can_write_with( 530 | run_ptr.offset(), 531 | run_ptr.size, 532 | tag, 533 | ) 534 | } 535 | 536 | pub fn has_mut(&self, run_ptr: RunPointer) -> bool { 537 | self.allocations[run_ptr.alloc_id].runs[run_ptr.run()] 538 | .has_mut(run_ptr.offset(), run_ptr.size) 539 | } 540 | } 541 | -------------------------------------------------------------------------------- /mir/src/serialize.rs: -------------------------------------------------------------------------------- 1 | use crate::{syntax::*, tyctxt::TyCtxt}; 2 | 3 | #[derive(Debug, Clone, Copy)] 4 | pub enum CallSynatx { 5 | /// Call(bb2, _1, fn2(_3)) 6 | /// Up to 2023-08-19 7 | V1, 8 | /// Call(_1 = fn2(_3), bb2) 9 | /// Up to 2023-11-14 10 | V2, 11 | /// Call(_1 = fn2(_3), bb2, UnwindUnreachable()) 12 | /// Uo to 2023-12-26 13 | V3, 14 | /// Call(_1 = fn2(_3), ReturnTo(bb2), UnwindUnreachable()) 15 | /// Current 16 | V4, 17 | } 18 | 19 | impl From<&str> for CallSynatx { 20 | fn from(value: &str) -> Self { 21 | match value { 22 | "v1" => Self::V1, 23 | "v2" => Self::V2, 24 | "v3" => Self::V3, 25 | "v4" => Self::V4, 26 | _ => panic!("invalid syntax version {value}"), 27 | } 28 | } 29 | } 30 | 31 | pub trait Serialize { 32 | fn serialize(&self, tcx: &TyCtxt) -> String; 33 | } 34 | 35 | impl Serialize for TyId { 36 | fn serialize(&self, tcx: &TyCtxt) -> String { 37 | match self.kind(tcx) { 38 | TyKind::Unit => "()".to_owned(), 39 | TyKind::Bool => "bool".to_owned(), 40 | TyKind::Char => "char".to_owned(), 41 | TyKind::Int(IntTy::Isize) => "isize".to_owned(), 42 | TyKind::Int(IntTy::I8) => "i8".to_owned(), 43 | TyKind::Int(IntTy::I16) => "i16".to_owned(), 44 | TyKind::Int(IntTy::I32) => "i32".to_owned(), 45 | TyKind::Int(IntTy::I64) => "i64".to_owned(), 46 | TyKind::Int(IntTy::I128) => "i128".to_owned(), 47 | 48 | TyKind::Uint(UintTy::Usize) => "usize".to_owned(), 49 | TyKind::Uint(UintTy::U8) => "u8".to_owned(), 50 | TyKind::Uint(UintTy::U16) => "u16".to_owned(), 51 | TyKind::Uint(UintTy::U32) => "u32".to_owned(), 52 | TyKind::Uint(UintTy::U64) => "u64".to_owned(), 53 | TyKind::Uint(UintTy::U128) => "u128".to_owned(), 54 | 55 | TyKind::Float(FloatTy::F32) => "f32".to_owned(), 56 | TyKind::Float(FloatTy::F64) => "f64".to_owned(), 57 | // Pointer types 58 | TyKind::RawPtr(ty, mutability) => { 59 | format!("{}{}", mutability.ptr_prefix_str(), ty.serialize(tcx)) 60 | } 61 | TyKind::Ref(ty, mutability) => { 62 | format!("&'static {}{}", mutability.prefix_str(), ty.serialize(tcx)) 63 | } 64 | // Sequence types 65 | TyKind::Tuple(elems) => { 66 | if elems.len() == 1 { 67 | format!("({},)", elems[0].serialize(tcx)) 68 | } else { 69 | format!("({})", elems.as_slice().serialize(tcx)) 70 | } 71 | } 72 | TyKind::Array(ty, len) => { 73 | format!("[{}; {len}]", ty.serialize(tcx)) 74 | } 75 | // User-defined type 76 | TyKind::Adt(_) => self.type_name(), 77 | } 78 | } 79 | } 80 | 81 | impl Serialize for &[TyId] { 82 | fn serialize(&self, tcx: &TyCtxt) -> String { 83 | self.iter() 84 | .map(|ty| ty.serialize(tcx)) 85 | .intersperse(", ".to_string()) 86 | .collect() 87 | } 88 | } 89 | 90 | impl Place { 91 | pub fn serialize_value(&self, tcx: &TyCtxt) -> String { 92 | let str = self.local().identifier(); 93 | self.projection().iter().fold(str, |acc, proj| match proj { 94 | ProjectionElem::Deref => format!("(*{acc})"), 95 | ProjectionElem::TupleField(id) => format!("{acc}.{}", id.index()), 96 | ProjectionElem::Field(id) => format!("{acc}.{}", id.identifier()), 97 | ProjectionElem::Index(local) => format!("{acc}[{}]", local.identifier()), 98 | ProjectionElem::DowncastField(vid, fid, ty) => format!( 99 | "Field::<{}>(Variant({acc}, {}), {})", 100 | ty.serialize(tcx), 101 | vid.index(), 102 | fid.index() 103 | ), 104 | ProjectionElem::ConstantIndex { offset } => format!("{acc}[{offset}]"), 105 | }) 106 | } 107 | 108 | // Place context needs place!() for Field(Variant()) 109 | pub fn serialize_place(&self, tcx: &TyCtxt) -> String { 110 | let str = self.local().identifier(); 111 | self.projection().iter().fold(str, |acc, proj| match proj { 112 | ProjectionElem::Deref => format!("(*{acc})"), 113 | ProjectionElem::TupleField(id) => format!("{acc}.{}", id.index()), 114 | ProjectionElem::Field(id) => format!("{acc}.{}", id.identifier()), 115 | ProjectionElem::Index(local) => format!("{acc}[{}]", local.identifier()), 116 | ProjectionElem::DowncastField(vid, fid, ty) => format!( 117 | "place!(Field::<{}>(Variant({acc}, {}), {}))", 118 | ty.serialize(tcx), 119 | vid.index(), 120 | fid.index() 121 | ), 122 | ProjectionElem::ConstantIndex { offset } => format!("{acc}[{offset}]"), 123 | }) 124 | } 125 | } 126 | 127 | impl Serialize for Literal { 128 | fn serialize(&self, tcx: &TyCtxt) -> String { 129 | match self { 130 | Literal::Uint(i, _) => format!("{i}_{}", self.ty().serialize(tcx)), 131 | Literal::Int(i, _) if *i < 0 => format!("({i}_{})", self.ty().serialize(tcx)), 132 | Literal::Int(i, _) => format!("{i}_{}", self.ty().serialize(tcx)), 133 | Literal::Float(f, _) => { 134 | if f.is_nan() { 135 | format!("{}::NAN", self.ty().serialize(tcx)) 136 | } else if f.is_infinite() { 137 | if f.is_sign_positive() { 138 | format!("{}::INFINITY", self.ty().serialize(tcx)) 139 | } else { 140 | format!("{}::NEG_INFINITY", self.ty().serialize(tcx)) 141 | } 142 | } else if *f < 0. { 143 | format!("({f}_{})", self.ty().serialize(tcx)) 144 | } else { 145 | format!("{f}_{}", self.ty().serialize(tcx)) 146 | } 147 | } 148 | Literal::Bool(b) => b.to_string(), 149 | Literal::Char(c) => format!("'\\u{{{:x}}}'", u32::from(*c)), 150 | } 151 | } 152 | } 153 | 154 | impl Serialize for Operand { 155 | fn serialize(&self, tcx: &TyCtxt) -> String { 156 | match self { 157 | Operand::Copy(place) => place.serialize_value(tcx), 158 | Operand::Move(place) => format!("Move({})", place.serialize_value(tcx)), 159 | Operand::Constant(lit) => lit.serialize(tcx), 160 | } 161 | } 162 | } 163 | 164 | impl Serialize for Rvalue { 165 | fn serialize(&self, tcx: &TyCtxt) -> String { 166 | match self { 167 | Rvalue::Use(a) => a.serialize(tcx), 168 | Rvalue::UnaryOp(op, a) => format!("{}{}", op.symbol(), a.serialize(tcx)), 169 | Rvalue::BinaryOp(BinOp::Offset, a, b) => { 170 | format!("Offset({}, {})", a.serialize(tcx), b.serialize(tcx)) 171 | } 172 | Rvalue::BinaryOp(op, a, b) => { 173 | format!("{} {} {}", a.serialize(tcx), op.symbol(), b.serialize(tcx)) 174 | } 175 | Rvalue::CheckedBinaryOp(op, a, b) => format!( 176 | "Checked({} {} {})", 177 | a.serialize(tcx), 178 | op.symbol(), 179 | b.serialize(tcx) 180 | ), 181 | 182 | Rvalue::Cast(a, target) => format!("{} as {}", a.serialize(tcx), target.serialize(tcx)), 183 | Rvalue::Len(place) => format!("Len({})", place.serialize_value(tcx)), 184 | Rvalue::Discriminant(place) => format!("Discriminant({})", place.serialize_value(tcx)), 185 | Rvalue::AddressOf(Mutability::Not, place) => { 186 | format!("core::ptr::addr_of!({})", place.serialize_place(tcx)) 187 | } 188 | Rvalue::AddressOf(Mutability::Mut, place) => { 189 | format!("core::ptr::addr_of_mut!({})", place.serialize_place(tcx)) 190 | } 191 | Rvalue::Ref(Mutability::Not, place) => { 192 | format!("&{}", place.serialize_place(tcx)) 193 | } 194 | Rvalue::Ref(Mutability::Mut, place) => { 195 | format!("&mut {}", place.serialize_place(tcx)) 196 | } 197 | Rvalue::Aggregate(kind, operands) => match kind { 198 | AggregateKind::Array(_) => { 199 | let list: String = operands 200 | .iter() 201 | .map(|op| op.serialize(tcx)) 202 | .intersperse(",".to_owned()) 203 | .collect(); 204 | format!("[{list}]") 205 | } 206 | AggregateKind::Tuple => match operands.len() { 207 | 0 => "()".to_owned(), 208 | 1 => format!("({},)", operands.first().unwrap().serialize(tcx)), 209 | _ => format!( 210 | "({})", 211 | operands 212 | .iter() 213 | .map(|l| l.serialize(tcx)) 214 | .intersperse(", ".to_owned()) 215 | .collect::() 216 | ), 217 | }, 218 | AggregateKind::Adt(ty, variant) => { 219 | let TyKind::Adt(adt) = ty.kind(tcx) else { 220 | panic!("not an adt"); 221 | }; 222 | let list: String = operands 223 | .iter_enumerated() 224 | .map(|(fid, op)| format!("{}: {}", fid.identifier(), op.serialize(tcx))) 225 | .intersperse(",".to_owned()) 226 | .collect(); 227 | if adt.is_enum() { 228 | format!("{}::{} {{ {list} }}", ty.type_name(), variant.identifier()) 229 | } else { 230 | format!("{} {{ {list} }}", ty.type_name()) 231 | } 232 | } 233 | }, 234 | } 235 | } 236 | } 237 | 238 | impl Serialize for Statement { 239 | fn serialize(&self, tcx: &TyCtxt) -> String { 240 | match self { 241 | Statement::Assign(place, rvalue) => { 242 | format!("{} = {}", place.serialize_place(tcx), rvalue.serialize(tcx)) 243 | } 244 | Statement::StorageLive(local) => format!("StorageLive({})", local.identifier()), 245 | Statement::StorageDead(local) => format!("StorageDead({})", local.identifier()), 246 | Statement::Deinit(local) => format!("Deinit({})", local.serialize_value(tcx)), 247 | Statement::SetDiscriminant(place, discr) => { 248 | format!("SetDiscriminant({}, {discr})", place.serialize_value(tcx)) 249 | } 250 | Statement::Retag(place) => format!("Retag({})", place.serialize_value(tcx)), 251 | Statement::Nop => String::default(), 252 | } 253 | } 254 | } 255 | 256 | impl Terminator { 257 | fn serialize(&self, tcx: &TyCtxt, call_syntax: CallSynatx) -> String { 258 | match self { 259 | Terminator::Return => "Return()".to_owned(), 260 | Terminator::Goto { target } => format!("Goto({})", target.identifier()), 261 | Terminator::Unreachable => "Unreachable()".to_owned(), 262 | Terminator::Drop { place, target } => { 263 | format!( 264 | "Drop({}, {})", 265 | place.serialize_value(tcx), 266 | target.identifier() 267 | ) 268 | } 269 | Terminator::Call { 270 | destination, 271 | target, 272 | callee, 273 | args, 274 | } => { 275 | let args_list: String = args 276 | .iter() 277 | .map(|arg| arg.serialize(tcx)) 278 | .intersperse(", ".to_owned()) 279 | .collect(); 280 | let fn_name = match callee { 281 | Callee::Generated(func) => func.identifier(), 282 | Callee::Named(func) => func.to_string(), 283 | Callee::Intrinsic(func) => format!("core::intrinsics::{func}"), 284 | }; 285 | match call_syntax { 286 | CallSynatx::V1 => format!( 287 | "Call({}, {}, {fn_name}({args_list}))", 288 | destination.serialize_place(tcx), 289 | target.identifier(), 290 | ), 291 | CallSynatx::V2 => format!( 292 | "Call({} = {fn_name}({args_list}), {})", 293 | destination.serialize_place(tcx), 294 | target.identifier(), 295 | ), 296 | CallSynatx::V3 => format!( 297 | "Call({} = {fn_name}({args_list}), {}, UnwindUnreachable())", 298 | destination.serialize_place(tcx), 299 | target.identifier(), 300 | ), 301 | CallSynatx::V4 => format!( 302 | "Call({} = {fn_name}({args_list}), ReturnTo({}), UnwindUnreachable())", 303 | destination.serialize_place(tcx), 304 | target.identifier(), 305 | ), 306 | } 307 | } 308 | Terminator::SwitchInt { discr, targets } => { 309 | let arms = targets.match_arms(); 310 | format!("match {} {{\n{}\n}}", discr.serialize(tcx), arms) 311 | } 312 | Terminator::Hole => unreachable!("hole"), 313 | } 314 | } 315 | } 316 | 317 | impl BasicBlockData { 318 | fn serialize(&self, tcx: &TyCtxt, call_syntax: CallSynatx) -> String { 319 | let mut stmts: String = self 320 | .statements 321 | .iter() 322 | .filter(|stmt| !matches!(stmt, Statement::Nop)) 323 | .map(|stmt| format!("{};\n", stmt.serialize(tcx))) 324 | .collect(); 325 | stmts.push_str(&self.terminator.serialize(tcx, call_syntax)); 326 | stmts 327 | } 328 | } 329 | 330 | impl Serialize for VariantDef { 331 | fn serialize(&self, tcx: &TyCtxt) -> String { 332 | self.fields 333 | .iter_enumerated() 334 | .map(|(id, ty)| format!("{}: {},\n", id.identifier(), ty.serialize(tcx))) 335 | .collect() 336 | } 337 | } 338 | 339 | impl Body { 340 | fn serialize(&self, tcx: &TyCtxt, call_syntax: CallSynatx) -> String { 341 | // Return type annotation 342 | let mut body: String = format!("type RET = {};\n", self.return_ty().serialize(tcx)); 343 | // Declarations 344 | body.extend(self.vars_iter().map(|idx| { 345 | let decl = &self.local_decls[idx]; 346 | format!("let {}: {};\n", idx.identifier(), decl.ty.serialize(tcx)) 347 | })); 348 | let mut bbs = self.basic_blocks.iter_enumerated(); 349 | // First bb 350 | body.push_str(&format!( 351 | "{{\n{}\n}}\n", 352 | bbs.next() 353 | .expect("body contains at least one bb") 354 | .1 355 | .serialize(tcx, call_syntax) 356 | )); 357 | // Other bbs 358 | body.extend(bbs.map(|(idx, bb)| { 359 | format!( 360 | "{} = {{\n{}\n}}\n", 361 | idx.identifier(), 362 | bb.serialize(tcx, call_syntax) 363 | ) 364 | })); 365 | format!("mir! {{\n{body}\n}}") 366 | } 367 | } 368 | 369 | impl Program { 370 | pub fn serialize(&self, tcx: &TyCtxt, call_syntax: CallSynatx) -> String { 371 | let mut program = Program::HEADER.to_string(); 372 | 373 | if self.use_debug_dumper { 374 | program += Program::DEBUG_DUMPER; 375 | } else { 376 | program += Program::DUMPER; 377 | } 378 | 379 | program.extend(self.functions.iter_enumerated().map(|(idx, body)| { 380 | let args_list: String = body 381 | .args_iter() 382 | .map(|arg| { 383 | let decl = &body.local_decls[arg]; 384 | format!( 385 | "{}{}: {}", 386 | decl.mutability.prefix_str(), 387 | arg.identifier(), 388 | decl.ty.serialize(tcx) 389 | ) 390 | }) 391 | .intersperse(",".to_string()) 392 | .collect(); 393 | format!( 394 | "{}\n{}fn {}({}) -> {} {{\n{}\n}}\n", 395 | Program::FUNCTION_ATTRIBUTE, 396 | if body.public { "pub " } else { "" }, 397 | idx.identifier(), 398 | args_list, 399 | body.return_ty().serialize(tcx), 400 | body.serialize(tcx, call_syntax) 401 | ) 402 | })); 403 | let arg_list: String = self 404 | .entry_args 405 | .iter() 406 | .map(|arg| format!("std::hint::black_box({})", arg.serialize(tcx))) 407 | .intersperse(", ".to_string()) 408 | .collect(); 409 | 410 | let first_fn: String = self 411 | .functions 412 | .indices() 413 | .next() 414 | .expect("program has functions") 415 | .identifier(); 416 | 417 | let hash_printer = if self.use_debug_dumper { 418 | "" 419 | } else { 420 | r#" 421 | unsafe { 422 | println!("hash: {}", H.finish()); 423 | } 424 | "# 425 | }; 426 | 427 | program.push_str(&format!( 428 | "pub fn main() {{ 429 | {first_fn}({arg_list}); 430 | {hash_printer} 431 | }}" 432 | )); 433 | program 434 | } 435 | } 436 | 437 | #[cfg(test)] 438 | mod tests { 439 | use super::Serialize; 440 | use crate::{syntax::*, tyctxt::TyCtxt}; 441 | use config::TyConfig; 442 | 443 | #[test] 444 | fn serialize_body() { 445 | let mut body = Body::new(&[TyCtxt::BOOL], TyCtxt::BOOL, true); 446 | let statements = vec![Statement::Assign( 447 | Place::RETURN_SLOT, 448 | Rvalue::UnaryOp(UnOp::Not, Operand::Copy(Place::from_local(Local::new(1)))), 449 | )]; 450 | body.new_basic_block(BasicBlockData { 451 | statements, 452 | terminator: Terminator::Return, 453 | }); 454 | } 455 | 456 | #[test] 457 | fn serialize_literal() { 458 | let tcx = TyCtxt::from_primitives(TyConfig::default()); 459 | let nan = Literal::Float(f32::NAN as f64, FloatTy::F32); 460 | assert_eq!(nan.serialize(&tcx), "f32::NAN"); 461 | 462 | let inf = Literal::Float(f32::INFINITY as f64, FloatTy::F32); 463 | assert_eq!(inf.serialize(&tcx), "f32::INFINITY"); 464 | } 465 | } 466 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "abi" 7 | version = "0.1.0" 8 | 9 | [[package]] 10 | name = "aho-corasick" 11 | version = "1.1.3" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 14 | dependencies = [ 15 | "memchr", 16 | ] 17 | 18 | [[package]] 19 | name = "anstream" 20 | version = "0.6.18" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 23 | dependencies = [ 24 | "anstyle", 25 | "anstyle-parse", 26 | "anstyle-query", 27 | "anstyle-wincon", 28 | "colorchoice", 29 | "is_terminal_polyfill", 30 | "utf8parse", 31 | ] 32 | 33 | [[package]] 34 | name = "anstyle" 35 | version = "1.0.10" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 38 | 39 | [[package]] 40 | name = "anstyle-parse" 41 | version = "0.2.6" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 44 | dependencies = [ 45 | "utf8parse", 46 | ] 47 | 48 | [[package]] 49 | name = "anstyle-query" 50 | version = "1.1.2" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 53 | dependencies = [ 54 | "windows-sys", 55 | ] 56 | 57 | [[package]] 58 | name = "anstyle-wincon" 59 | version = "3.0.7" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 62 | dependencies = [ 63 | "anstyle", 64 | "once_cell", 65 | "windows-sys", 66 | ] 67 | 68 | [[package]] 69 | name = "autocfg" 70 | version = "1.4.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 73 | 74 | [[package]] 75 | name = "bimap" 76 | version = "0.6.3" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" 79 | 80 | [[package]] 81 | name = "bitflags" 82 | version = "2.8.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" 85 | 86 | [[package]] 87 | name = "byteorder" 88 | version = "1.5.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 91 | 92 | [[package]] 93 | name = "cfg-if" 94 | version = "1.0.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 97 | 98 | [[package]] 99 | name = "clap" 100 | version = "4.5.26" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" 103 | dependencies = [ 104 | "clap_builder", 105 | ] 106 | 107 | [[package]] 108 | name = "clap_builder" 109 | version = "4.5.26" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" 112 | dependencies = [ 113 | "anstream", 114 | "anstyle", 115 | "clap_lex", 116 | "strsim", 117 | ] 118 | 119 | [[package]] 120 | name = "clap_lex" 121 | version = "0.7.4" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 124 | 125 | [[package]] 126 | name = "colorchoice" 127 | version = "1.0.3" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 130 | 131 | [[package]] 132 | name = "colored" 133 | version = "2.2.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" 136 | dependencies = [ 137 | "lazy_static", 138 | "windows-sys", 139 | ] 140 | 141 | [[package]] 142 | name = "config" 143 | version = "0.1.0" 144 | dependencies = [ 145 | "serde", 146 | "toml", 147 | ] 148 | 149 | [[package]] 150 | name = "crossbeam-deque" 151 | version = "0.8.6" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 154 | dependencies = [ 155 | "crossbeam-epoch", 156 | "crossbeam-utils", 157 | ] 158 | 159 | [[package]] 160 | name = "crossbeam-epoch" 161 | version = "0.9.18" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 164 | dependencies = [ 165 | "crossbeam-utils", 166 | ] 167 | 168 | [[package]] 169 | name = "crossbeam-utils" 170 | version = "0.8.21" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 173 | 174 | [[package]] 175 | name = "difftest" 176 | version = "0.1.0" 177 | dependencies = [ 178 | "clap", 179 | "colored", 180 | "config", 181 | "env_logger", 182 | "log", 183 | "rayon", 184 | "serde", 185 | "tempfile", 186 | ] 187 | 188 | [[package]] 189 | name = "either" 190 | version = "1.13.0" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 193 | 194 | [[package]] 195 | name = "env_filter" 196 | version = "0.1.3" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" 199 | dependencies = [ 200 | "log", 201 | "regex", 202 | ] 203 | 204 | [[package]] 205 | name = "env_logger" 206 | version = "0.11.6" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" 209 | dependencies = [ 210 | "anstream", 211 | "anstyle", 212 | "env_filter", 213 | "humantime", 214 | "log", 215 | ] 216 | 217 | [[package]] 218 | name = "equivalent" 219 | version = "1.0.1" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 222 | 223 | [[package]] 224 | name = "errno" 225 | version = "0.3.10" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 228 | dependencies = [ 229 | "libc", 230 | "windows-sys", 231 | ] 232 | 233 | [[package]] 234 | name = "fastrand" 235 | version = "2.3.0" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 238 | 239 | [[package]] 240 | name = "fixedbitset" 241 | version = "0.4.2" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 244 | 245 | [[package]] 246 | name = "generate" 247 | version = "0.1.0" 248 | dependencies = [ 249 | "abi", 250 | "bimap", 251 | "clap", 252 | "config", 253 | "env_logger", 254 | "index_vec", 255 | "log", 256 | "mir", 257 | "petgraph", 258 | "rand", 259 | "rand_distr", 260 | "rangemap", 261 | "smallvec", 262 | ] 263 | 264 | [[package]] 265 | name = "getrandom" 266 | version = "0.2.15" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 269 | dependencies = [ 270 | "cfg-if", 271 | "libc", 272 | "wasi 0.11.0+wasi-snapshot-preview1", 273 | ] 274 | 275 | [[package]] 276 | name = "getrandom" 277 | version = "0.3.3" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 280 | dependencies = [ 281 | "cfg-if", 282 | "libc", 283 | "r-efi", 284 | "wasi 0.14.2+wasi-0.2.4", 285 | ] 286 | 287 | [[package]] 288 | name = "hashbrown" 289 | version = "0.15.2" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 292 | 293 | [[package]] 294 | name = "humantime" 295 | version = "2.1.0" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 298 | 299 | [[package]] 300 | name = "index_vec" 301 | version = "0.1.4" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "44faf5bb8861a9c72e20d3fb0fdbd59233e43056e2b80475ab0aacdc2e781355" 304 | 305 | [[package]] 306 | name = "indexmap" 307 | version = "2.7.0" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" 310 | dependencies = [ 311 | "equivalent", 312 | "hashbrown", 313 | ] 314 | 315 | [[package]] 316 | name = "is_terminal_polyfill" 317 | version = "1.70.1" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 320 | 321 | [[package]] 322 | name = "lazy_static" 323 | version = "1.5.0" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 326 | 327 | [[package]] 328 | name = "libc" 329 | version = "0.2.169" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 332 | 333 | [[package]] 334 | name = "libm" 335 | version = "0.2.11" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" 338 | 339 | [[package]] 340 | name = "linux-raw-sys" 341 | version = "0.4.15" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 344 | 345 | [[package]] 346 | name = "log" 347 | version = "0.4.25" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" 350 | 351 | [[package]] 352 | name = "memchr" 353 | version = "2.7.4" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 356 | 357 | [[package]] 358 | name = "mir" 359 | version = "0.1.0" 360 | dependencies = [ 361 | "config", 362 | "index_vec", 363 | "smallvec", 364 | ] 365 | 366 | [[package]] 367 | name = "num-traits" 368 | version = "0.2.19" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 371 | dependencies = [ 372 | "autocfg", 373 | "libm", 374 | ] 375 | 376 | [[package]] 377 | name = "once_cell" 378 | version = "1.20.2" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 381 | 382 | [[package]] 383 | name = "petgraph" 384 | version = "0.6.5" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" 387 | dependencies = [ 388 | "fixedbitset", 389 | "indexmap", 390 | ] 391 | 392 | [[package]] 393 | name = "ppv-lite86" 394 | version = "0.2.20" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 397 | dependencies = [ 398 | "zerocopy", 399 | ] 400 | 401 | [[package]] 402 | name = "proc-macro2" 403 | version = "1.0.93" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 406 | dependencies = [ 407 | "unicode-ident", 408 | ] 409 | 410 | [[package]] 411 | name = "quote" 412 | version = "1.0.38" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 415 | dependencies = [ 416 | "proc-macro2", 417 | ] 418 | 419 | [[package]] 420 | name = "r-efi" 421 | version = "5.2.0" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 424 | 425 | [[package]] 426 | name = "rand" 427 | version = "0.9.1" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" 430 | dependencies = [ 431 | "rand_chacha", 432 | "rand_core", 433 | ] 434 | 435 | [[package]] 436 | name = "rand_chacha" 437 | version = "0.9.0" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 440 | dependencies = [ 441 | "ppv-lite86", 442 | "rand_core", 443 | ] 444 | 445 | [[package]] 446 | name = "rand_core" 447 | version = "0.9.3" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 450 | dependencies = [ 451 | "getrandom 0.3.3", 452 | ] 453 | 454 | [[package]] 455 | name = "rand_distr" 456 | version = "0.5.1" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" 459 | dependencies = [ 460 | "num-traits", 461 | "rand", 462 | ] 463 | 464 | [[package]] 465 | name = "rangemap" 466 | version = "0.1.0" 467 | dependencies = [ 468 | "abi", 469 | ] 470 | 471 | [[package]] 472 | name = "rayon" 473 | version = "1.10.0" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 476 | dependencies = [ 477 | "either", 478 | "rayon-core", 479 | ] 480 | 481 | [[package]] 482 | name = "rayon-core" 483 | version = "1.12.1" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 486 | dependencies = [ 487 | "crossbeam-deque", 488 | "crossbeam-utils", 489 | ] 490 | 491 | [[package]] 492 | name = "regex" 493 | version = "1.11.1" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 496 | dependencies = [ 497 | "aho-corasick", 498 | "memchr", 499 | "regex-automata", 500 | "regex-syntax", 501 | ] 502 | 503 | [[package]] 504 | name = "regex-automata" 505 | version = "0.4.9" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 508 | dependencies = [ 509 | "aho-corasick", 510 | "memchr", 511 | "regex-syntax", 512 | ] 513 | 514 | [[package]] 515 | name = "regex-syntax" 516 | version = "0.8.5" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 519 | 520 | [[package]] 521 | name = "rustix" 522 | version = "0.38.43" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" 525 | dependencies = [ 526 | "bitflags", 527 | "errno", 528 | "libc", 529 | "linux-raw-sys", 530 | "windows-sys", 531 | ] 532 | 533 | [[package]] 534 | name = "serde" 535 | version = "1.0.219" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 538 | dependencies = [ 539 | "serde_derive", 540 | ] 541 | 542 | [[package]] 543 | name = "serde_derive" 544 | version = "1.0.219" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 547 | dependencies = [ 548 | "proc-macro2", 549 | "quote", 550 | "syn", 551 | ] 552 | 553 | [[package]] 554 | name = "serde_spanned" 555 | version = "0.6.8" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 558 | dependencies = [ 559 | "serde", 560 | ] 561 | 562 | [[package]] 563 | name = "smallvec" 564 | version = "1.13.2" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 567 | 568 | [[package]] 569 | name = "strsim" 570 | version = "0.11.1" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 573 | 574 | [[package]] 575 | name = "syn" 576 | version = "2.0.96" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" 579 | dependencies = [ 580 | "proc-macro2", 581 | "quote", 582 | "unicode-ident", 583 | ] 584 | 585 | [[package]] 586 | name = "tempfile" 587 | version = "3.15.0" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" 590 | dependencies = [ 591 | "cfg-if", 592 | "fastrand", 593 | "getrandom 0.2.15", 594 | "once_cell", 595 | "rustix", 596 | "windows-sys", 597 | ] 598 | 599 | [[package]] 600 | name = "toml" 601 | version = "0.8.22" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" 604 | dependencies = [ 605 | "serde", 606 | "serde_spanned", 607 | "toml_datetime", 608 | "toml_edit", 609 | ] 610 | 611 | [[package]] 612 | name = "toml_datetime" 613 | version = "0.6.9" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" 616 | dependencies = [ 617 | "serde", 618 | ] 619 | 620 | [[package]] 621 | name = "toml_edit" 622 | version = "0.22.26" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" 625 | dependencies = [ 626 | "indexmap", 627 | "serde", 628 | "serde_spanned", 629 | "toml_datetime", 630 | "toml_write", 631 | "winnow", 632 | ] 633 | 634 | [[package]] 635 | name = "toml_write" 636 | version = "0.1.1" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" 639 | 640 | [[package]] 641 | name = "unicode-ident" 642 | version = "1.0.14" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 645 | 646 | [[package]] 647 | name = "utf8parse" 648 | version = "0.2.2" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 651 | 652 | [[package]] 653 | name = "wasi" 654 | version = "0.11.0+wasi-snapshot-preview1" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 657 | 658 | [[package]] 659 | name = "wasi" 660 | version = "0.14.2+wasi-0.2.4" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 663 | dependencies = [ 664 | "wit-bindgen-rt", 665 | ] 666 | 667 | [[package]] 668 | name = "windows-sys" 669 | version = "0.59.0" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 672 | dependencies = [ 673 | "windows-targets", 674 | ] 675 | 676 | [[package]] 677 | name = "windows-targets" 678 | version = "0.52.6" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 681 | dependencies = [ 682 | "windows_aarch64_gnullvm", 683 | "windows_aarch64_msvc", 684 | "windows_i686_gnu", 685 | "windows_i686_gnullvm", 686 | "windows_i686_msvc", 687 | "windows_x86_64_gnu", 688 | "windows_x86_64_gnullvm", 689 | "windows_x86_64_msvc", 690 | ] 691 | 692 | [[package]] 693 | name = "windows_aarch64_gnullvm" 694 | version = "0.52.6" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 697 | 698 | [[package]] 699 | name = "windows_aarch64_msvc" 700 | version = "0.52.6" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 703 | 704 | [[package]] 705 | name = "windows_i686_gnu" 706 | version = "0.52.6" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 709 | 710 | [[package]] 711 | name = "windows_i686_gnullvm" 712 | version = "0.52.6" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 715 | 716 | [[package]] 717 | name = "windows_i686_msvc" 718 | version = "0.52.6" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 721 | 722 | [[package]] 723 | name = "windows_x86_64_gnu" 724 | version = "0.52.6" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 727 | 728 | [[package]] 729 | name = "windows_x86_64_gnullvm" 730 | version = "0.52.6" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 733 | 734 | [[package]] 735 | name = "windows_x86_64_msvc" 736 | version = "0.52.6" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 739 | 740 | [[package]] 741 | name = "winnow" 742 | version = "0.7.10" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" 745 | dependencies = [ 746 | "memchr", 747 | ] 748 | 749 | [[package]] 750 | name = "wit-bindgen-rt" 751 | version = "0.39.0" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 754 | dependencies = [ 755 | "bitflags", 756 | ] 757 | 758 | [[package]] 759 | name = "zerocopy" 760 | version = "0.7.35" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 763 | dependencies = [ 764 | "byteorder", 765 | "zerocopy-derive", 766 | ] 767 | 768 | [[package]] 769 | name = "zerocopy-derive" 770 | version = "0.7.35" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 773 | dependencies = [ 774 | "proc-macro2", 775 | "quote", 776 | "syn", 777 | ] 778 | -------------------------------------------------------------------------------- /mir/src/syntax.rs: -------------------------------------------------------------------------------- 1 | use std::num::TryFromIntError; 2 | 3 | use index_vec::{IndexVec, define_index_type}; 4 | use smallvec::SmallVec; 5 | 6 | use crate::tyctxt::TyCtxt; 7 | 8 | #[derive(Clone)] 9 | pub struct Program { 10 | pub functions: IndexVec, 11 | pub entry_args: Vec, 12 | pub use_debug_dumper: bool, 13 | } 14 | 15 | pub type LocalDecls = IndexVec; 16 | 17 | define_index_type! {pub struct Function = u32;} 18 | #[derive(Clone)] 19 | pub struct Body { 20 | pub basic_blocks: IndexVec, 21 | pub local_decls: LocalDecls, 22 | arg_count: usize, 23 | pub public: bool, 24 | } 25 | 26 | define_index_type! {pub struct BasicBlock = u32;} 27 | #[derive(Clone)] 28 | pub struct BasicBlockData { 29 | pub(crate) statements: Vec, 30 | pub(crate) terminator: Terminator, 31 | } 32 | 33 | impl BasicBlockData { 34 | /// An empty basic block 35 | pub fn new() -> Self { 36 | Self { 37 | statements: vec![], 38 | terminator: Terminator::Hole, 39 | } 40 | } 41 | 42 | pub fn insert_statement(&mut self, stmt: Statement) { 43 | self.statements.push(stmt); 44 | } 45 | 46 | pub fn set_terminator(&mut self, term: Terminator) { 47 | assert!(matches!(self.terminator, Terminator::Hole)); 48 | self.terminator = term; 49 | } 50 | 51 | pub fn terminator(&self) -> &Terminator { 52 | &self.terminator 53 | } 54 | } 55 | 56 | define_index_type! {pub struct Local = u32;} 57 | #[derive(Clone)] 58 | pub struct LocalDecl { 59 | /// Whether this is a mutable binding (i.e., `let x` or `let mut x`). 60 | /// 61 | /// Temporaries and the return place are always mutable. 62 | pub mutability: Mutability, 63 | 64 | /// If this local is a temporary and `is_block_tail` is `Some`, 65 | /// The type of this local. 66 | pub ty: TyId, 67 | } 68 | 69 | define_index_type! {pub struct FieldIdx = u32;} 70 | define_index_type! {pub struct VariantIdx = u32;} 71 | impl FieldIdx { 72 | pub fn identifier(&self) -> String { 73 | format!("fld{}", self.index()) 74 | } 75 | } 76 | impl VariantIdx { 77 | pub fn identifier(&self) -> String { 78 | format!("Variant{}", self.index()) 79 | } 80 | } 81 | 82 | #[derive(Clone, PartialEq, Eq, Debug)] 83 | pub struct Place { 84 | local: Local, 85 | projection: SmallVec<[ProjectionElem; 4]>, 86 | } 87 | impl Place { 88 | pub const fn from_local(local: Local) -> Self { 89 | Place { 90 | local, 91 | projection: SmallVec::new_const(), 92 | } 93 | } 94 | 95 | pub fn from_projected(local: Local, projections: &[ProjectionElem]) -> Self { 96 | Place { 97 | local, 98 | projection: SmallVec::from(projections), 99 | } 100 | } 101 | 102 | pub fn local(&self) -> Local { 103 | self.local 104 | } 105 | 106 | pub fn projection(&self) -> &[ProjectionElem] { 107 | &self.projection 108 | } 109 | 110 | pub fn project(&mut self, proj: ProjectionElem) -> &mut Self { 111 | // TODO: validation 112 | self.projection.push(proj); 113 | self 114 | } 115 | 116 | pub fn ty(&self, local_decl: &LocalDecls, tcx: &TyCtxt) -> TyId { 117 | local_decl[self.local()] 118 | .ty 119 | .projected_ty(tcx, self.projection()) 120 | } 121 | } 122 | 123 | impl From for Place { 124 | fn from(value: Local) -> Self { 125 | Self::from_local(value) 126 | } 127 | } 128 | 129 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 130 | pub enum ProjectionElem { 131 | Deref, 132 | /// This should be the same as Field, but to allow for context free serialization 133 | /// we separate it out 134 | TupleField(FieldIdx), 135 | Field(FieldIdx), 136 | Index(Local), 137 | // We need the field ty because serialisaion needs it 138 | DowncastField(VariantIdx, FieldIdx, TyId), 139 | ConstantIndex { 140 | offset: u64, 141 | }, 142 | // TODO: Subslice 143 | } 144 | 145 | impl ProjectionElem { 146 | pub fn is_deref(&self) -> bool { 147 | *self == ProjectionElem::Deref 148 | } 149 | } 150 | 151 | #[derive(Clone, Copy)] 152 | pub enum Callee { 153 | Generated(Function), 154 | Named(&'static str), 155 | Intrinsic(&'static str), 156 | } 157 | 158 | #[derive(Clone)] 159 | pub enum Terminator { 160 | Hole, 161 | // define!("mir_return", fn Return() -> BasicBlock); 162 | Return, 163 | // define!("mir_goto", fn Goto(destination: BasicBlock) -> BasicBlock); 164 | Goto { 165 | target: BasicBlock, 166 | }, 167 | // define!("mir_unreachable", fn Unreachable() -> BasicBlock); 168 | Unreachable, 169 | // define!("mir_drop", fn Drop(place: T, goto: BasicBlock)); 170 | Drop { 171 | place: Place, 172 | target: BasicBlock, 173 | }, 174 | // define!("mir_call", fn Call(place: T, goto: BasicBlock, call: T)); 175 | Call { 176 | callee: Callee, 177 | destination: Place, 178 | target: BasicBlock, 179 | args: Vec, 180 | }, 181 | /// Switches based on the computed value. 182 | /// 183 | /// First, evaluates the `discr` operand. The type of the operand must be a signed or unsigned 184 | /// integer, char, or bool, and must match the given type. Then, if the list of switch targets 185 | /// contains the computed value, continues execution at the associated basic block. Otherwise, 186 | /// continues execution at the "otherwise" basic block. 187 | /// 188 | /// Target values may not appear more than once. 189 | SwitchInt { 190 | /// The discriminant value being tested. 191 | discr: Operand, 192 | targets: SwitchTargets, 193 | }, 194 | } 195 | 196 | #[derive(Clone)] 197 | pub struct SwitchTargets { 198 | pub branches: Vec<(u128, BasicBlock)>, 199 | pub otherwise: BasicBlock, 200 | } 201 | 202 | #[derive(Clone)] 203 | pub enum Rvalue { 204 | Use(Operand), 205 | UnaryOp(UnOp, Operand), 206 | BinaryOp(BinOp, Operand, Operand), 207 | Cast(Operand, TyId), 208 | // define!("mir_checked", fn Checked(binop: T) -> (T, bool)); 209 | CheckedBinaryOp(BinOp, Operand, Operand), 210 | // define!("mir_len", fn Len(place: T) -> usize); 211 | Len(Place), 212 | // define!("mir_discriminant",fn Discriminant(place: T) -> ::Discriminant); 213 | Discriminant(Place), 214 | AddressOf(Mutability, Place), 215 | Aggregate(AggregateKind, IndexVec), 216 | Ref(Mutability, Place), 217 | } 218 | 219 | #[derive(Clone, Copy)] 220 | pub enum AggregateKind { 221 | /// The type is of the element 222 | Array(TyId), 223 | Tuple, 224 | 225 | Adt(TyId, VariantIdx), 226 | } 227 | 228 | #[derive(Debug, Clone, Copy)] 229 | pub enum Literal { 230 | Uint(u128, UintTy), 231 | Int(i128, IntTy), 232 | Bool(bool), 233 | Char(char), 234 | // Every f32 can be expressed exactly as f64 235 | Float(f64, FloatTy), 236 | } 237 | 238 | #[derive(Clone, Debug)] 239 | pub enum Operand { 240 | Copy(Place), 241 | // define!("mir_move", fn Move(place: T) -> T); 242 | Move(Place), 243 | Constant(Literal), 244 | // TODO: the following 245 | // define!("mir_static", fn Static(s: T) -> &'static T); 246 | // define!("mir_static_mut", fn StaticMut(s: T) -> *mut T); 247 | } 248 | 249 | impl Operand { 250 | pub fn ty(&self, local_decls: &LocalDecls, tcx: &TyCtxt) -> TyId { 251 | match self { 252 | Operand::Copy(place) | Operand::Move(place) => place.ty(local_decls, tcx), 253 | Operand::Constant(lit) => lit.ty(), 254 | } 255 | } 256 | 257 | pub fn place(&self) -> Option<&Place> { 258 | match self { 259 | Operand::Copy(place) | Operand::Move(place) => Some(place), 260 | Operand::Constant(..) => None, 261 | } 262 | } 263 | } 264 | 265 | #[derive(Clone)] 266 | pub enum Statement { 267 | Assign(Place, Rvalue), 268 | // define!("mir_storage_live", fn StorageLive(local: T)); 269 | StorageLive(Local), 270 | // define!("mir_storage_dead", fn StorageDead(local: T)); 271 | StorageDead(Local), 272 | // define!("mir_storage_live", fn StorageLive(local: T)); 273 | Deinit(Place), 274 | // define!("mir_set_discriminant", fn SetDiscriminant(place: T, index: u32)); 275 | SetDiscriminant(Place, u32), 276 | // define!("mir_retag", fn Retag(place: T)); 277 | Retag(Place), 278 | Nop, 279 | } 280 | 281 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] 282 | pub enum Mutability { 283 | // N.B. Order is deliberate, so that Not < Mut 284 | Not, 285 | Mut, 286 | } 287 | 288 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] 289 | pub enum IntTy { 290 | Isize, 291 | I8, 292 | I16, 293 | I32, 294 | I64, 295 | I128, 296 | } 297 | 298 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] 299 | pub enum UintTy { 300 | Usize, 301 | U8, 302 | U16, 303 | U32, 304 | U64, 305 | U128, 306 | } 307 | 308 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] 309 | pub enum FloatTy { 310 | F32, 311 | F64, 312 | } 313 | 314 | define_index_type! {pub struct TyId = u32;} 315 | impl TyId { 316 | pub fn type_name(&self) -> String { 317 | format!("Adt{}", self.index()) 318 | } 319 | } 320 | impl TyId { 321 | pub fn kind(self, tcx: &TyCtxt) -> &TyKind { 322 | tcx.kind(self) 323 | } 324 | pub fn tuple_elems(self, tcx: &TyCtxt) -> Option<&[TyId]> { 325 | match self.kind(tcx) { 326 | TyKind::Tuple(tys) => Some(tys), 327 | _ => None, 328 | } 329 | } 330 | 331 | pub fn is_checked_binary_op_lhs(self, tcx: &TyCtxt) -> bool { 332 | match self.kind(tcx) { 333 | TyKind::Tuple(elems) => matches!( 334 | ( 335 | elems.get(0).map(|ty| ty.kind(tcx)), 336 | elems.get(1).map(|ty| ty.kind(tcx)) 337 | ), 338 | ( 339 | Some(TyKind::Int(..) | TyKind::Float(..) | TyKind::Uint(..)), 340 | Some(TyKind::Bool) 341 | ) 342 | ), 343 | _ => false, 344 | } 345 | } 346 | 347 | // TODO: are pointers scalar? 348 | pub fn is_scalar(self, tcx: &TyCtxt) -> bool { 349 | self.kind(tcx).is_scalar() 350 | } 351 | 352 | pub fn projected_ty(self, tcx: &TyCtxt, projs: &[ProjectionElem]) -> Self { 353 | match projs { 354 | [] => self, 355 | [head, tail @ ..] => { 356 | let projected = match head { 357 | ProjectionElem::Deref => match self.kind(tcx) { 358 | TyKind::RawPtr(pointee, ..) | TyKind::Ref(pointee, ..) => *pointee, 359 | _ => panic!("not a reference"), 360 | }, 361 | ProjectionElem::TupleField(idx) => { 362 | self.tuple_elems(tcx).expect("is a tuple")[idx.index()] 363 | } 364 | ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. } => { 365 | match self.kind(tcx) { 366 | TyKind::Array(ty, ..) => *ty, 367 | _ => panic!("not an array"), 368 | } 369 | } 370 | ProjectionElem::Field(fid) => match self.kind(tcx) { 371 | TyKind::Adt(adt) => { 372 | let fields = &adt.variants.first().expect("adt is a struct").fields; 373 | fields[*fid] 374 | } 375 | _ => panic!("not an adt"), 376 | }, 377 | ProjectionElem::DowncastField(vid, fid, _) => match self.kind(tcx) { 378 | TyKind::Adt(adt) => adt.variants[*vid].fields[*fid], 379 | _ => panic!("not an adt"), 380 | }, 381 | }; 382 | projected.projected_ty(tcx, tail) 383 | } 384 | } 385 | } 386 | 387 | pub fn contains

(self, tcx: &TyCtxt, predicate: P) -> bool 388 | where 389 | P: Fn(&TyCtxt, TyId) -> bool + Copy, 390 | { 391 | if predicate(tcx, self) { 392 | return true; 393 | } 394 | match self.kind(tcx) { 395 | TyKind::Tuple(elems) => elems.iter().any(|ty| ty.contains(tcx, predicate)), 396 | TyKind::RawPtr(pointee, _) => pointee.contains(tcx, predicate), 397 | TyKind::Array(ty, ..) => ty.contains(tcx, predicate), 398 | TyKind::Adt(adt) => adt 399 | .variants 400 | .iter() 401 | .any(|variant| variant.fields.iter().any(|ty| ty.contains(tcx, predicate))), 402 | _ => false, 403 | } 404 | } 405 | 406 | pub fn is_ref(self, tcx: &TyCtxt) -> bool { 407 | matches!(self.kind(tcx), TyKind::Ref(..)) 408 | } 409 | 410 | pub fn is_raw_ptr(self, tcx: &TyCtxt) -> bool { 411 | matches!(self.kind(tcx), TyKind::RawPtr(..)) 412 | } 413 | 414 | pub fn is_any_ptr(self, tcx: &TyCtxt) -> bool { 415 | matches!(self.kind(tcx), TyKind::RawPtr(..) | TyKind::Ref(..)) 416 | } 417 | 418 | pub fn pointee_ty(self, tcx: &TyCtxt) -> Option { 419 | match self.kind(tcx) { 420 | TyKind::RawPtr(ty, ..) | TyKind::Ref(ty, ..) => Some(*ty), 421 | _ => None, 422 | } 423 | } 424 | 425 | // If doesn't contain printer 426 | pub fn determ_printable(self, tcx: &TyCtxt) -> bool { 427 | !self.contains(tcx, |tcx, ty| ty.is_any_ptr(tcx)) 428 | } 429 | 430 | pub fn hashable(self, tcx: &TyCtxt) -> bool { 431 | // TODO: hash Adts maybe 432 | self.kind(tcx).is_structural() 433 | && self.determ_printable(tcx) 434 | && !self.contains(tcx, |_, ty| { 435 | ty == TyCtxt::F32 || ty == TyCtxt::F64 || ty.kind(tcx).is_adt() 436 | }) 437 | && self != TyCtxt::UNIT 438 | } 439 | 440 | pub fn is_copy(self, tcx: &TyCtxt) -> bool { 441 | let kind = self.kind(tcx); 442 | match kind { 443 | TyKind::Unit 444 | | TyKind::Bool 445 | | TyKind::Char 446 | | TyKind::Int(_) 447 | | TyKind::Uint(_) 448 | | TyKind::Float(_) => true, 449 | TyKind::RawPtr(_, _) | TyKind::Ref(_, _) => false, 450 | TyKind::Tuple(tys) => tys.iter().all(|ty| ty.is_copy(tcx)), 451 | TyKind::Array(ty, _) => ty.is_copy(tcx), 452 | TyKind::Adt(_) => tcx.meta(self).copy, 453 | } 454 | } 455 | } 456 | 457 | #[derive(Clone, Debug)] 458 | pub enum TyKind { 459 | // Scalars 460 | Unit, 461 | Bool, 462 | Char, 463 | Int(IntTy), 464 | Uint(UintTy), 465 | Float(FloatTy), 466 | // Composite 467 | RawPtr(TyId, Mutability), 468 | Ref(TyId, Mutability), 469 | Tuple(Vec), 470 | // User-defined 471 | Adt(Adt), 472 | Array(TyId, usize), 473 | // TODO: more types 474 | } 475 | 476 | impl TyKind { 477 | pub const ISIZE: Self = TyKind::Int(IntTy::Isize); 478 | pub const I8: Self = TyKind::Int(IntTy::I8); 479 | pub const I16: Self = TyKind::Int(IntTy::I16); 480 | pub const I32: Self = TyKind::Int(IntTy::I32); 481 | pub const I64: Self = TyKind::Int(IntTy::I64); 482 | pub const I128: Self = TyKind::Int(IntTy::I128); 483 | pub const USIZE: Self = TyKind::Uint(UintTy::Usize); 484 | pub const U8: Self = TyKind::Uint(UintTy::U8); 485 | pub const U16: Self = TyKind::Uint(UintTy::U16); 486 | pub const U32: Self = TyKind::Uint(UintTy::U32); 487 | pub const U64: Self = TyKind::Uint(UintTy::U64); 488 | pub const U128: Self = TyKind::Uint(UintTy::U128); 489 | pub const F32: Self = TyKind::Float(FloatTy::F32); 490 | pub const F64: Self = TyKind::Float(FloatTy::F64); 491 | 492 | pub const INTS: [Self; 6] = [ 493 | Self::ISIZE, 494 | Self::I8, 495 | Self::I16, 496 | Self::I32, 497 | Self::I64, 498 | Self::I128, 499 | ]; 500 | 501 | pub const UINTS: [Self; 6] = [ 502 | Self::USIZE, 503 | Self::U8, 504 | Self::U16, 505 | Self::U32, 506 | Self::U64, 507 | Self::U128, 508 | ]; 509 | 510 | pub const FLOATS: [Self; 2] = [Self::F32, Self::F64]; 511 | 512 | pub fn is_scalar(&self) -> bool { 513 | match self { 514 | &TyKind::Int(..) 515 | | &TyKind::Uint(..) 516 | | &TyKind::Bool 517 | | &TyKind::Char 518 | | &TyKind::Unit => true, 519 | _ => false, 520 | } 521 | } 522 | 523 | pub fn is_structural(&self) -> bool { 524 | !matches!(self, TyKind::Adt(..)) 525 | } 526 | 527 | pub fn is_adt(&self) -> bool { 528 | matches!(self, TyKind::Adt(..)) 529 | } 530 | 531 | pub fn is_enum(&self) -> bool { 532 | match self { 533 | TyKind::Adt(adt) => adt.is_enum(), 534 | _ => false, 535 | } 536 | } 537 | } 538 | 539 | impl PartialEq for TyKind { 540 | fn eq(&self, other: &Self) -> bool { 541 | match (self, other) { 542 | (Self::Int(l0), Self::Int(r0)) => l0 == r0, 543 | (Self::Uint(l0), Self::Uint(r0)) => l0 == r0, 544 | (Self::Float(l0), Self::Float(r0)) => l0 == r0, 545 | (Self::RawPtr(l0, l1), Self::RawPtr(r0, r1)) => l0 == r0 && l1 == r1, 546 | (Self::Ref(l0, l1), Self::Ref(r0, r1)) => l0 == r0 && l1 == r1, 547 | (Self::Tuple(l0), Self::Tuple(r0)) => l0 == r0, 548 | (Self::Array(l0, l1), Self::Array(r0, r1)) => l0 == r0 && l1 == r1, 549 | (Self::Adt(..), Self::Adt(..)) => false, 550 | _ => core::mem::discriminant(self) == core::mem::discriminant(other), 551 | } 552 | } 553 | } 554 | 555 | #[derive(PartialEq, Eq, Clone, Hash, Debug)] 556 | pub struct VariantDef { 557 | /// Fields of this variant. 558 | pub fields: IndexVec, 559 | } 560 | 561 | #[derive(Clone, Hash, Debug)] 562 | pub struct Adt { 563 | pub variants: IndexVec, 564 | } 565 | impl Adt { 566 | pub fn copy_derivable(&self, tcx: &TyCtxt) -> bool { 567 | self.variants 568 | .iter() 569 | .all(|variant| variant.fields.iter().all(|ty| ty.is_copy(tcx))) 570 | } 571 | 572 | pub fn is_enum(&self) -> bool { 573 | self.variants.len() > 1 574 | } 575 | } 576 | 577 | #[derive(Clone, Copy)] 578 | pub enum BinOp { 579 | Add, 580 | Sub, 581 | Mul, 582 | Div, 583 | Rem, 584 | BitXor, 585 | BitAnd, 586 | BitOr, 587 | Shl, 588 | Shr, 589 | Eq, 590 | Lt, 591 | Le, 592 | Ne, 593 | Ge, 594 | Gt, 595 | Offset, 596 | } 597 | 598 | #[derive(Clone, Copy)] 599 | pub enum UnOp { 600 | Not, 601 | Neg, 602 | } 603 | 604 | impl Literal { 605 | // TODO: this doesn't need tcx 606 | pub fn ty(&self) -> TyId { 607 | match self { 608 | Literal::Int(_, IntTy::I8) => TyCtxt::I8, 609 | Literal::Int(_, IntTy::I16) => TyCtxt::I16, 610 | Literal::Int(_, IntTy::I32) => TyCtxt::I32, 611 | Literal::Int(_, IntTy::I64) => TyCtxt::I64, 612 | Literal::Int(_, IntTy::I128) => TyCtxt::I128, 613 | Literal::Int(_, IntTy::Isize) => TyCtxt::ISIZE, 614 | Literal::Uint(_, UintTy::U8) => TyCtxt::U8, 615 | Literal::Uint(_, UintTy::U16) => TyCtxt::U16, 616 | Literal::Uint(_, UintTy::U32) => TyCtxt::U32, 617 | Literal::Uint(_, UintTy::U64) => TyCtxt::U64, 618 | Literal::Uint(_, UintTy::U128) => TyCtxt::U128, 619 | Literal::Uint(_, UintTy::Usize) => TyCtxt::USIZE, 620 | Literal::Bool(_) => TyCtxt::BOOL, 621 | Literal::Char(_) => TyCtxt::CHAR, 622 | Literal::Float(_, FloatTy::F32) => TyCtxt::F32, 623 | Literal::Float(_, FloatTy::F64) => TyCtxt::F64, 624 | } 625 | } 626 | } 627 | 628 | impl From for Literal { 629 | fn from(value: bool) -> Self { 630 | Self::Bool(value) 631 | } 632 | } 633 | 634 | impl From for Literal { 635 | fn from(value: char) -> Self { 636 | Self::Char(value) 637 | } 638 | } 639 | 640 | impl From for Literal { 641 | fn from(value: u8) -> Self { 642 | Self::Uint(value as u128, UintTy::U8) 643 | } 644 | } 645 | impl From for Literal { 646 | fn from(value: u16) -> Self { 647 | Self::Uint(value as u128, UintTy::U16) 648 | } 649 | } 650 | impl From for Literal { 651 | fn from(value: u32) -> Self { 652 | Self::Uint(value as u128, UintTy::U32) 653 | } 654 | } 655 | impl From for Literal { 656 | fn from(value: u64) -> Self { 657 | Self::Uint(value as u128, UintTy::U64) 658 | } 659 | } 660 | impl From for Literal { 661 | fn from(value: u128) -> Self { 662 | Self::Uint(value, UintTy::U128) 663 | } 664 | } 665 | impl TryFrom for Literal { 666 | type Error = TryFromIntError; 667 | 668 | fn try_from(value: usize) -> Result { 669 | Ok(Self::Uint(value.try_into()?, UintTy::Usize)) 670 | } 671 | } 672 | impl From for Literal { 673 | fn from(value: i8) -> Self { 674 | Self::Int(value as i128, IntTy::I8) 675 | } 676 | } 677 | impl From for Literal { 678 | fn from(value: i16) -> Self { 679 | Self::Int(value as i128, IntTy::I16) 680 | } 681 | } 682 | impl From for Literal { 683 | fn from(value: i32) -> Self { 684 | Self::Int(value as i128, IntTy::I32) 685 | } 686 | } 687 | impl From for Literal { 688 | fn from(value: i64) -> Self { 689 | Self::Int(value as i128, IntTy::I64) 690 | } 691 | } 692 | impl From for Literal { 693 | fn from(value: i128) -> Self { 694 | Self::Int(value, IntTy::I128) 695 | } 696 | } 697 | impl TryFrom for Literal { 698 | type Error = TryFromIntError; 699 | 700 | fn try_from(value: isize) -> Result { 701 | Ok(Self::Int(value.try_into()?, IntTy::Isize)) 702 | } 703 | } 704 | 705 | impl From for Literal { 706 | fn from(value: f32) -> Self { 707 | Self::Float(value as f64, FloatTy::F32) 708 | } 709 | } 710 | 711 | impl From for Literal { 712 | fn from(value: f64) -> Self { 713 | Self::Float(value, FloatTy::F64) 714 | } 715 | } 716 | 717 | impl Program { 718 | pub const FUNCTION_ATTRIBUTE: &'static str = 719 | "#[custom_mir(dialect = \"runtime\", phase = \"initial\")]"; 720 | pub const HEADER: &'static str = "#![recursion_limit = \"1024\"] 721 | #![feature(custom_mir, core_intrinsics, lazy_get)] 722 | #![allow(unused_parens, unused_assignments, overflowing_literals)] 723 | extern crate core; 724 | use core::intrinsics::mir::*;\n"; 725 | 726 | pub const DUMPER: &'static str = r#" 727 | use std::collections::hash_map::DefaultHasher; 728 | use std::hash::{Hash, Hasher}; 729 | use std::sync::LazyLock; 730 | 731 | static mut H: LazyLock = LazyLock::new(|| DefaultHasher::new()); 732 | 733 | #[inline(never)] 734 | fn dump_var( 735 | val0: impl Hash, 736 | val1: impl Hash, 737 | val2: impl Hash, 738 | val3: impl Hash, 739 | ) { 740 | unsafe { 741 | val0.hash(LazyLock::force_mut(&mut H)); 742 | val1.hash(LazyLock::force_mut(&mut H)); 743 | val2.hash(LazyLock::force_mut(&mut H)); 744 | val3.hash(LazyLock::force_mut(&mut H)); 745 | } 746 | } 747 | "#; 748 | 749 | pub const DEBUG_DUMPER: &'static str = r#" 750 | use std::fmt::Debug; 751 | 752 | #[inline(never)] 753 | fn dump_var( 754 | f: usize, 755 | var0: usize, val0: impl Debug, 756 | var1: usize, val1: impl Debug, 757 | var2: usize, val2: impl Debug, 758 | var3: usize, val3: impl Debug, 759 | ) { 760 | println!("fn{f}:_{var0} = {val0:?}\n_{var1} = {val1:?}\n_{var2} = {val2:?}\n_{var3} = {val3:?}"); 761 | } 762 | "#; 763 | 764 | // Fake "intrinsic" 765 | pub const DUMPER_CALL: Callee = Callee::Named("dump_var"); 766 | pub const DUMPER_ARITY: usize = 4; 767 | 768 | // A new, empty function 769 | pub fn new(debug: bool) -> Self { 770 | Self { 771 | functions: IndexVec::default(), 772 | entry_args: vec![], 773 | use_debug_dumper: debug, 774 | } 775 | } 776 | 777 | pub fn push_fn(&mut self, body: Body) -> Function { 778 | self.functions.push(body) 779 | } 780 | 781 | pub fn set_entry_args(&mut self, args: &[Literal]) { 782 | self.entry_args = Vec::from(args); 783 | } 784 | } 785 | 786 | impl Function { 787 | pub fn identifier(&self) -> String { 788 | format!("fn{}", self.index()) 789 | } 790 | } 791 | 792 | impl SwitchTargets { 793 | pub fn match_arms(&self) -> String { 794 | let mut arms: String = self 795 | .branches 796 | .iter() 797 | .map(|(val, bb)| format!("{val} => {},\n", bb.identifier())) 798 | .collect(); 799 | arms.push_str(&format!("_ => {}", self.otherwise.identifier())); 800 | arms 801 | } 802 | } 803 | 804 | impl LocalDecl { 805 | pub fn new_mut(ty: TyId) -> Self { 806 | Self { 807 | mutability: Mutability::Mut, 808 | ty, 809 | } 810 | } 811 | } 812 | 813 | impl Local { 814 | pub const RET: Self = Self::from_raw_unchecked(0); 815 | pub fn identifier(&self) -> String { 816 | if *self == Self::RET { 817 | "RET".to_owned() 818 | } else { 819 | format!("_{}", self.index()) 820 | } 821 | } 822 | } 823 | 824 | impl Body { 825 | pub fn new(args: &[TyId], return_ty: TyId, public: bool) -> Self { 826 | let mut locals = IndexVec::new(); 827 | locals.push(LocalDecl::new_mut(return_ty)); 828 | // TODO: args shouldn't always be mut 829 | args.iter().for_each(|ty| { 830 | locals.push(LocalDecl::new_mut(*ty)); 831 | }); 832 | Self { 833 | basic_blocks: IndexVec::new(), 834 | local_decls: locals, 835 | public, 836 | arg_count: args.len(), 837 | } 838 | } 839 | 840 | pub fn is_arg(&self, local: Local) -> bool { 841 | (1..self.arg_count).contains(&local.index()) 842 | } 843 | 844 | /// Returns an iterator over function arguments 845 | pub fn args_iter(&self) -> impl ExactSizeIterator { 846 | (1..self.arg_count + 1).map(Local::new) 847 | } 848 | 849 | pub fn args_decl_iter(&self) -> impl ExactSizeIterator { 850 | self.args_iter() 851 | .map(|local| (local, &self.local_decls[local])) 852 | } 853 | 854 | /// Returns an iterator over locals defined in the function body 855 | pub fn vars_iter(&self) -> impl ExactSizeIterator { 856 | (self.arg_count + 1..self.local_decls.len()).map(Local::new) 857 | } 858 | 859 | pub fn vars_decl_iter(&self) -> impl ExactSizeIterator { 860 | self.vars_iter() 861 | .map(|local| (local, &self.local_decls[local])) 862 | } 863 | 864 | pub fn return_ty(&self) -> TyId { 865 | self.local_decls[Local::RET].ty 866 | } 867 | 868 | pub fn declare_new_var(&mut self, mutability: Mutability, ty: TyId) -> Local { 869 | self.local_decls.push(LocalDecl { mutability, ty }) 870 | } 871 | 872 | pub fn new_basic_block(&mut self, bb: BasicBlockData) -> BasicBlock { 873 | self.basic_blocks.push(bb) 874 | } 875 | } 876 | 877 | impl BasicBlock { 878 | pub fn identifier(&self) -> String { 879 | format!("bb{}", self.index()) 880 | } 881 | } 882 | 883 | impl Mutability { 884 | /// Returns `""` (empty string) or `"mut "` depending on the mutability. 885 | pub fn prefix_str(&self) -> &'static str { 886 | match self { 887 | Mutability::Mut => "mut ", 888 | Mutability::Not => "", 889 | } 890 | } 891 | 892 | pub fn ptr_prefix_str(&self) -> &'static str { 893 | match self { 894 | Mutability::Mut => "*mut ", 895 | Mutability::Not => "*const ", 896 | } 897 | } 898 | } 899 | 900 | impl Place { 901 | pub const RETURN_SLOT: Self = Self::from_local(Local::RET); 902 | } 903 | 904 | impl BinOp { 905 | pub fn symbol(&self) -> &'static str { 906 | match self { 907 | BinOp::Add => "+", 908 | BinOp::Sub => "-", 909 | BinOp::Mul => "*", 910 | BinOp::Div => "/", 911 | BinOp::Rem => "%", 912 | BinOp::BitXor => "^", 913 | BinOp::BitAnd => "&", 914 | BinOp::BitOr => "|", 915 | BinOp::Shl => "<<", 916 | BinOp::Shr => ">>", 917 | BinOp::Eq => "==", 918 | BinOp::Lt => "<", 919 | BinOp::Le => "<=", 920 | BinOp::Ne => "!=", 921 | BinOp::Ge => ">=", 922 | BinOp::Gt => ">", 923 | BinOp::Offset => panic!("offset is not a real infix operator"), 924 | } 925 | } 926 | } 927 | 928 | impl UnOp { 929 | pub fn symbol(&self) -> &'static str { 930 | match self { 931 | UnOp::Not => "!", 932 | UnOp::Neg => "-", 933 | } 934 | } 935 | } 936 | --------------------------------------------------------------------------------