#![allow(
clippy::cast_precision_loss,
clippy::let_underscore_untyped,
clippy::module_name_repetitions,
clippy::struct_excessive_bools,
clippy::too_many_lines,
clippy::uninlined_format_args,
clippy::unnecessary_map_or,
clippy::unseparated_literal_suffix,
clippy::unwrap_or_default
)]
mod cmd;
mod count;
mod error;
mod opts;
mod table;
use crate::cmd::CommandExt as _;
use crate::count::{count_lines, Instantiations};
use crate::error::{Error, Result};
use crate::opts::{Coloring, LlvmLines, SortOrder, Subcommand};
use clap::{CommandFactory, Parser};
use regex::Regex;
use std::collections::HashMap as Map;
use std::env;
use std::ffi::OsString;
use std::fs;
use std::io::{self, BufRead, IsTerminal, Write};
use std::path::{Path, PathBuf};
use std::process::{self, Command, Stdio};
use tempfile::TempDir;
use termcolor::{Color::Green, ColorChoice, ColorSpec, StandardStream, WriteColor as _};
cargo_subcommand_metadata::description!(
"Count the number of lines of LLVM IR across all instantiations of a generic function"
);
fn main() {
let Subcommand::LlvmLines(opts) = Subcommand::parse();
if opts.help {
let _ = Subcommand::command()
.get_subcommands_mut()
.next()
.unwrap()
.print_help();
return;
}
if opts.version {
let mut stdout = io::stdout();
let _ = stdout.write_all(Subcommand::command().render_version().as_bytes());
return;
}
let result = if opts.files.is_empty() {
cargo_llvm_lines(&opts)
} else {
read_llvm_ir_from_paths(&opts.files, opts.sort, opts.filter.as_ref())
};
process::exit(match result {
Ok(code) => code,
Err(err) => {
let _ = writeln!(io::stderr(), "{}", err);
1
}
});
}
fn cargo_llvm_lines(opts: &LlvmLines) -> Result<i32> {
let outdir = tempfile::Builder::new()
.prefix("cargo-llvm-lines")
.tempdir()
.expect("failed to create tmp file");
let outfile = outdir.path().join("crate");
let cargo = env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo"));
let mut cmd = Command::new(cargo);
propagate_opts(&mut cmd, opts, &outfile);
cmd.env("CARGO_INCREMENTAL", "");
cmd.stdout(Stdio::inherit());
if opts.verbose {
let color = opts.color.unwrap_or(Coloring::Auto);
print_command(&cmd, color)?;
}
let exit = filter_err(&mut cmd)?;
if exit != 0 {
return Ok(exit);
}
let ir = read_llvm_ir_from_dir(&outdir)?;
let mut instantiations = Map::<String, Instantiations>::new();
count_lines(&mut instantiations, &ir);
table::print(instantiations, opts.sort, opts.filter.as_ref());
Ok(0)
}
fn read_llvm_ir_from_dir(outdir: &TempDir) -> Result<Vec<u8>> {
for file in fs::read_dir(outdir)? {
let path = file?.path();
if let Some(ext) = path.extension() {
if ext == "ll" {
let content = fs::read(path)?;
return Ok(content);
}
}
}
Err(Error::Msg("Ran --emit=llvm-ir but did not find output IR"))
}
fn read_llvm_ir_from_paths(
paths: &[PathBuf],
sort_order: SortOrder,
function_filter: Option<&Regex>,
) -> Result<i32> {
let mut instantiations = Map::<String, Instantiations>::new();
for path in paths {
match fs::read(path) {
Ok(ir) => count_lines(&mut instantiations, &ir),
Err(err) => return Err(Error::PathIo(path.clone(), err)),
}
}
table::print(instantiations, sort_order, function_filter);
Ok(0)
}
fn propagate_opts(cmd: &mut Command, opts: &LlvmLines, outfile: &Path) {
let LlvmLines {
sort: _,
filter: _,
files: _,
help: _,
version: _,
verbose,
quiet,
color,
ref config,
ref nightly_only_flags,
ref package,
lib,
ref bin,
ref example,
ref test,
ref bench,
ref features,
all_features,
no_default_features,
jobs,
release,
ref profile,
ref target,
ref target_dir,
ref manifest_path,
frozen,
locked,
offline,
ref rest,
} = *opts;
cmd.arg("rustc");
if verbose {
cmd.arg("--verbose");
}
if quiet {
cmd.arg("--quiet");
}
match color {
Some(Coloring::Always) => cmd.flag_value("--color", "always"),
Some(Coloring::Never) => cmd.flag_value("--color", "never"),
None | Some(Coloring::Auto) => {
if env::var_os("NO_COLOR").is_none() && io::stderr().is_terminal() {
cmd.flag_value("--color", "always");
} else {
cmd.flag_value("--color", "never");
}
}
}
for kv in config {
cmd.flag_value("--config", kv);
}
for flag in nightly_only_flags {
cmd.arg(format!("-Z{}", flag));
}
if let Some(package) = package {
cmd.flag_value("--package", package);
}
if lib {
cmd.arg("--lib");
}
if let Some(bin) = bin {
cmd.flag_value("--bin", bin);
}
if let Some(example) = example {
cmd.flag_value("--example", example);
}
if let Some(test) = test {
cmd.flag_value("--test", test);
}
if let Some(bench) = bench {
cmd.flag_value("--bench", bench);
}
if let Some(features) = features {
cmd.flag_value("--features", features);
}
if all_features {
cmd.arg("--all-features");
}
if no_default_features {
cmd.arg("--no-default-features");
}
if let Some(jobs) = jobs {
cmd.flag_value("--jobs", jobs.to_string());
}
if release {
cmd.arg("--release");
}
if let Some(profile) = profile {
cmd.flag_value("--profile", profile);
}
if let Some(target) = target {
cmd.flag_value("--target", target);
}
if let Some(target_dir) = target_dir {
cmd.flag_value("--target-dir", target_dir);
}
if let Some(manifest_path) = manifest_path {
cmd.flag_value("--manifest-path", manifest_path);
}
if frozen {
cmd.arg("--frozen");
}
if locked {
cmd.arg("--locked");
}
if offline {
cmd.arg("--offline");
}
cmd.arg("--");
cmd.flag_value("--emit", "llvm-ir");
cmd.arg("-Cno-prepopulate-passes");
cmd.arg("-Cpasses=name-anon-globals");
cmd.arg("-o");
cmd.arg(outfile);
cmd.args(rest);
}
fn filter_err(cmd: &mut Command) -> Result<i32> {
let mut child = cmd.stderr(Stdio::piped()).spawn()?;
let mut stderr = io::BufReader::new(child.stderr.take().unwrap());
let mut line = String::new();
while let Ok(n) = stderr.read_line(&mut line) {
if n == 0 {
break;
}
if !ignore_cargo_err(&line) {
let _ = write!(io::stderr(), "{}", line);
}
line.clear();
}
let code = child.wait()?.code().unwrap_or(1);
Ok(code)
}
fn ignore_cargo_err(line: &str) -> bool {
if line.trim().is_empty() {
return true;
}
let discarded_lines = [
"warnings emitted",
"ignoring specified output filename because multiple outputs were \
requested",
"ignoring specified output filename for 'link' output because multiple \
outputs were requested",
"ignoring --out-dir flag due to -o flag",
"due to multiple output types requested, the explicitly specified \
output file name will be adapted for each output type",
"ignoring -C extra-filename flag due to -o flag",
];
for s in &discarded_lines {
if line.contains(s) {
return true;
}
}
if let Some(i) = line.find(") generated ") {
let rest = &line[i + ") generated ".len()..];
let n = rest.bytes().take_while(u8::is_ascii_digit).count();
if n > 0 && rest[n..].starts_with(" warning") {
return true;
}
}
false
}
fn print_command(cmd: &Command, color: Coloring) -> Result<()> {
let mut shell_words = String::new();
let quoter = shlex::Quoter::new().allow_nul(true);
for arg in cmd.get_args() {
let arg_lossy = arg.to_string_lossy();
shell_words.push(' ');
match arg_lossy.split_once('=') {
Some((flag, value)) if flag.starts_with('-') && flag == quoter.quote(flag)? => {
shell_words.push_str(flag);
shell_words.push('=');
if !value.is_empty() {
shell_words.push_str("er.quote(value)?);
}
}
_ => shell_words.push_str("er.quote(&arg_lossy)?),
}
}
let color_choice = match color {
Coloring::Auto => ColorChoice::Auto,
Coloring::Always => ColorChoice::Always,
Coloring::Never => ColorChoice::Never,
};
let mut stream = StandardStream::stderr(color_choice);
let _ = stream.set_color(ColorSpec::new().set_bold(true).set_fg(Some(Green)));
let _ = write!(stream, "{:>12}", "Running");
let _ = stream.reset();
let _ = writeln!(stream, " `cargo{}`", shell_words);
Ok(())
}