rllvm 0.4.4

A tool to build whole-program LLVM bitcode files
Documentation
use std::{fs, path::PathBuf};

use clap::Parser;
use owo_colors::OwoColorize;
use rllvm::{
    bitcode_info::{BitcodeInfo, analyze_bitcode},
    cli::InfoArgs,
    error::Error,
    utils::extract_bitcode_filepaths_from_object_file,
};

/// Detect whether a file is an LLVM bitcode file by checking its magic bytes.
fn is_bitcode_file(path: &PathBuf) -> Result<bool, Error> {
    let data = fs::read(path)?;
    if data.len() < 4 {
        return Ok(false);
    }
    let head = [data[0], data[1], data[2], data[3]];

    // Raw bitcode begins with 'BC' 0xC0 0xDE.
    let raw = head == [0x42, 0x43, 0xC0, 0xDE];

    // On Darwin, clang emits the bitcode *wrapper* format instead, whose header
    // magic is 0x0B17C0DE. Checking only for 'BC' rejected every bitcode file
    // produced on macOS, which is the default output there.
    let wrapped = u32::from_le_bytes(head) == 0x0B17_C0DE;

    Ok(raw || wrapped)
}

/// Try to parse as an object file to check for embedded bitcode.
fn try_extract_bitcode_from_object(path: &PathBuf) -> Result<Option<PathBuf>, Error> {
    let data = fs::read(path)?;
    if object::File::parse(&*data).is_ok() {
        let bc_paths = extract_bitcode_filepaths_from_object_file(path)?;
        if let Some(first) = bc_paths.into_iter().next()
            && first.exists()
        {
            return Ok(Some(first));
        }
    }
    Ok(None)
}

fn print_info(info: &BitcodeInfo, show_functions: bool) {
    println!("{}", "=== Bitcode Info ===".bold());
    println!("File         : {}", info.file_path.display());
    println!("File size    : {} bytes", info.file_size);
    if let Some(triple) = &info.target_triple {
        println!("Target triple: {}", triple);
    }
    if let Some(layout) = &info.data_layout {
        println!("Data layout  : {}", layout);
    }
    println!("Functions    : {}", info.functions.len());
    println!("Basic blocks : {}", info.total_basic_blocks);
    println!("Instructions : {}", info.total_instructions);

    if show_functions && !info.functions.is_empty() {
        println!();
        println!("{}", "=== Functions ===".bold());
        for func in &info.functions {
            println!(
                "  {} (blocks: {}, instructions: {})",
                func.name.green(),
                func.basic_block_count,
                func.instruction_count,
            );
        }
    }
}

fn main() -> Result<(), Error> {
    let args = InfoArgs::parse();

    let input = &args.input;
    let input_path = input
        .canonicalize()
        .map_err(|e| Error::MissingFile(format!("Cannot resolve input path {:?}: {}", input, e)))?;

    // Determine the bitcode file to analyze
    let bc_path = if is_bitcode_file(&input_path)? {
        input_path
    } else {
        // Try extracting from an object file
        match try_extract_bitcode_from_object(&input_path)? {
            Some(path) => path,
            None => {
                return Err(Error::InvalidArguments(format!(
                    "{} is not a bitcode file and no embedded bitcode was found",
                    input.display()
                )));
            }
        }
    };

    let info = analyze_bitcode(&bc_path)?;
    print_info(&info, args.functions);

    Ok(())
}