Path: blob/main/cranelift/src/souper_harvest.rs
1691 views
use crate::utils::{iterate_files, read_to_string};1use anyhow::{Context as _, Result};2use clap::Parser;3use cranelift_codegen::Context;4use cranelift_codegen::control::ControlPlane;5use cranelift_codegen::ir::Function;6use cranelift_reader::parse_sets_and_triple;7use rayon::iter::{IntoParallelIterator, ParallelBridge, ParallelIterator};8use std::collections::HashSet;9use std::hash::{BuildHasher, BuildHasherDefault};10use std::io::Write;11use std::path::PathBuf;12use std::{fs, io};1314/// Harvest candidates for superoptimization from a Wasm or Clif file.15///16/// Candidates are emitted in Souper's text format:17/// <https://github.com/google/souper>18#[derive(Parser)]19pub struct Options {20/// Specify an input file to be used. Use '-' for stdin.21input: Vec<PathBuf>,2223/// Specify the directory where harvested left-hand side files should be24/// written to.25#[arg(short, long)]26output_dir: PathBuf,2728/// Configure Cranelift settings29#[arg(long = "set")]30settings: Vec<String>,3132/// Specify the Cranelift target33#[arg(long = "target")]34target: String,3536/// Add a comment from which CLIF variable and function each left-hand side37/// was harvested from. This prevents deduplicating harvested left-hand38/// sides.39#[arg(long)]40add_harvest_source: bool,41}4243pub fn run(options: &Options) -> Result<()> {44let parsed = parse_sets_and_triple(&options.settings, &options.target)?;45let fisa = parsed.as_fisa();46if fisa.isa.is_none() {47anyhow::bail!("`souper-harvest` requires a target isa");48}4950match fs::create_dir_all(&options.output_dir) {51Ok(_) => {}52Err(e)53if e.kind() == io::ErrorKind::AlreadyExists54&& fs::metadata(&options.output_dir)55.with_context(|| {56format!(57"failed to read file metadata: {}",58options.output_dir.display(),59)60})?61.is_dir() => {}62Err(e) => {63return Err(e).context(format!(64"failed to create output directory: {}",65options.output_dir.display()66));67}68}6970let (send, recv) = std::sync::mpsc::channel::<String>();7172let writing_thread = std::thread::spawn({73let output_dir = options.output_dir.clone();74let keep_harvest_source = options.add_harvest_source;75move || -> Result<()> {76let mut already_harvested = HashSet::new();77for lhs in recv {78let lhs = if keep_harvest_source {79&lhs80} else {81// Remove the first `;; Harvested from v12 in u:34` line.82let i = lhs.find('\n').unwrap();83&lhs[i + 1..]84};85let hash = hash(lhs.as_bytes());86if already_harvested.insert(hash) {87let output_path = output_dir.join(hash.to_string());88let mut output =89io::BufWriter::new(fs::File::create(&output_path).with_context(|| {90format!("failed to create file: {}", output_path.display())91})?);92output.write_all(lhs.as_bytes()).with_context(|| {93format!("failed to write to output file: {}", output_path.display())94})?;95}96}97Ok(())98}99});100101iterate_files(&options.input)102.par_bridge()103.flat_map(|path| {104parse_input(path)105.unwrap_or_else(|e| {106println!("{e:?}");107Vec::new()108})109.into_par_iter()110})111.map_init(112move || (send.clone(), Context::new()),113move |(send, ctx), func| {114ctx.clear();115ctx.func = func;116117ctx.optimize(fisa.isa.unwrap(), &mut ControlPlane::default())118.context("failed to run optimizations")?;119120ctx.souper_harvest(send)121.context("failed to run souper harvester")?;122123Ok(())124},125)126.collect::<Result<()>>()?;127128match writing_thread.join() {129Ok(result) => result?,130Err(e) => std::panic::resume_unwind(e),131}132133Ok(())134}135136fn parse_input(path: PathBuf) -> Result<Vec<Function>> {137let contents = read_to_string(&path)?;138let funcs = cranelift_reader::parse_functions(&contents)139.with_context(|| format!("parse error in {}", path.display()))?;140Ok(funcs)141}142143/// A convenience function for a quick usize hash144#[inline]145pub fn hash<T: std::hash::Hash + ?Sized>(v: &T) -> usize {146BuildHasherDefault::<rustc_hash::FxHasher>::default().hash_one(v) as usize147}148149150