cranefack-cli 0.4.2

Commandline utility for the cranefack brainfuck compiler
use crate::utils::read_input;
use cranefack::CompiledJitModule;
use cranefack::{
    optimize_with_config, parse, CraneFackError, Interpreter, OptimizeConfig, Program,
};
use std::error::Error;
use std::ffi::OsStr;
use std::io::Cursor;
use std::time::SystemTime;

pub fn benchmark_file(
    path: &OsStr,
    iterations: usize,
    runs: usize,
    optimized_only: bool,
    jit_only: bool,
) -> Result<(), Box<dyn Error>> {
    let source = read_input(path)?;

    let program = match parse(&source) {
        Ok(program) => program,
        Err(err) => {
            return err.pretty_print(&source, Some(&path.to_string_lossy()));
        }
    };

    let mut o0 = OptimizeConfig::o0();
    o0.jit_level = Some("none".to_owned());
    let mut o0_fast = OptimizeConfig::o0();
    o0_fast.jit_level = Some("speed".to_owned());

    let mut o1 = OptimizeConfig::o1();
    o1.jit_level = Some("none".to_owned());
    let mut o1_fast = OptimizeConfig::o1();
    o1_fast.jit_level = Some("speed".to_owned());

    let mut o2 = OptimizeConfig::o2();
    o2.jit_level = Some("none".to_owned());
    let mut o2_fast = OptimizeConfig::o2();
    o2_fast.jit_level = Some("speed".to_owned());

    let mut o3 = OptimizeConfig::o2();
    o3.jit_level = Some("none".to_owned());
    let mut o3_fast = OptimizeConfig::o2();
    o3_fast.jit_level = Some("speed".to_owned());

    let program_o0 = program.clone();

    let mut program_o1 = program.clone();
    optimize_with_config(&mut program_o1, &o1);

    let mut program_o2 = program.clone();
    optimize_with_config(&mut program_o2, &o2);

    let mut program_o3 = program;
    optimize_with_config(&mut program_o3, &o3);

    let mut results = [0u128; 12];

    let total = iterations * runs;

    println!(
        "Starting benchmark with {} iterations and {} runs per iteration",
        iterations, runs
    );

    for iteration in 0..iterations {
        println!("Iteration {} ", iteration + 1);

        if !jit_only {
            if !optimized_only {
                println!("Run interpreter with O0");
                results[0] += run_interpreter(&program_o0, runs)?;
            }

            println!("Run interpreter with O1");
            results[1] += run_interpreter(&program_o1, runs)?;

            println!("Run interpreter with O2");
            results[2] += run_interpreter(&program_o2, runs)?;

            println!("Run interpreter with O3");
            results[3] += run_interpreter(&program_o3, runs)?;
        }

        if !optimized_only {
            println!("Run jit with O0");
            results[4] += run_jit(&program_o0, runs, &o0)?;

            println!("Run jit with O0 speed");
            results[5] += run_jit(&program_o0, runs, &o0_fast)?;
        }

        println!("Run jit with O1");
        results[6] += run_jit(&program_o1, runs, &o1)?;

        println!("Run jit with O1 speed");
        results[7] += run_jit(&program_o1, runs, &o1_fast)?;

        println!("Run jit with O2");
        results[8] += run_jit(&program_o2, runs, &o2)?;

        println!("Run jit with O2 speed");
        results[9] += run_jit(&program_o2, runs, &o2_fast)?;

        println!("Run jit with O3");
        results[10] += run_jit(&program_o3, runs, &o3)?;

        println!("Run jit with O3 speed");
        results[11] += run_jit(&program_o3, runs, &o3_fast)?;
    }

    println!("Results:");

    if !jit_only {
        if !optimized_only {
            println!("Int O0       {} ms/run", get_millis(results[0], total));
        }
        println!("Int O1       {} ms/run", get_millis(results[1], total));
        println!("Int O2       {} ms/run", get_millis(results[2], total));
        println!("Int O3       {} ms/run", get_millis(results[3], total));
    }

    if !optimized_only {
        println!("Jit O0       {} ms/run", get_millis(results[4], total));
        println!("Jit O0 speed {} ms/run", get_millis(results[5], total));
    }
    println!("Jit O1       {} ms/run", get_millis(results[6], total));
    println!("Jit O1 speed {} ms/run", get_millis(results[7], total));
    println!("Jit O2       {} ms/run", get_millis(results[8], total));
    println!("Jit O2 speed {} ms/run", get_millis(results[9], total));
    println!("Jit O3       {} ms/run", get_millis(results[10], total));
    println!("Jit O3 speed {} ms/run", get_millis(results[11], total));

    Ok(())
}

fn get_millis(nanos: u128, total: usize) -> String {
    let per_iter_micros = nanos / total as u128 / 1000;

    let millis = per_iter_micros as f64 / 1000.0;

    let mut result = format!("{:0.3}", millis);

    while result.len() < 10 {
        result.insert(0, ' ');
    }

    result
}

fn run_interpreter(program: &Program, runs: usize) -> Result<u128, Box<dyn Error>> {
    let mut input = Cursor::new(b"");
    let mut output = Vec::new();

    let ts = SystemTime::now();

    for _ in 0..runs {
        input.set_position(0);
        output.clear();

        Interpreter::new(&mut input, &mut output).execute(program)?;
    }

    Ok(ts.elapsed()?.as_nanos())
}

fn run_jit(
    program: &Program,
    runs: usize,
    opt_mode: &OptimizeConfig,
) -> Result<u128, Box<dyn Error>> {
    let mut input = Cursor::new(b"");
    let mut output = Vec::new();

    let module = CompiledJitModule::new(program, opt_mode)?;

    let ts = SystemTime::now();

    for _ in 0..runs {
        input.set_position(0);
        output.clear();

        module.execute(&mut input, &mut output);
    }

    Ok(ts.elapsed()?.as_nanos())
}