//
// Syd: rock-solid application kernel
// benches/sandbox/elf.rs: ELF parser microbenchmarks
//
// Copyright (c) 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{
    fs::{read_dir, File},
    hint::black_box,
    io,
    num::NonZeroUsize,
    path::Path,
    ptr::NonNull,
    time::Duration,
};

use brunch::{benches, Bench};
use goblin::{elf::Elf, options::ParseOptions};
use libc::c_void;
use nix::sys::mman::{mmap, munmap, MapFlags, ProtFlags};
use syd::elf::ExecutableFile;

const STDPATH: &[&str] = &["/usr/bin", "/bin", "/usr/sbin", "/sbin"];

struct MmapFile {
    ptr: NonNull<c_void>,
    len: usize,
}

impl MmapFile {
    fn map_readonly(file: File) -> io::Result<Self> {
        let len_u64 = file.metadata()?.len();
        let len: usize = len_u64
            .try_into()
            .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "file too large"))?;
        let len = NonZeroUsize::new(len)
            .ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "empty file"))?;

        let ptr = unsafe {
            mmap(
                None,
                len,
                ProtFlags::PROT_READ,
                MapFlags::MAP_PRIVATE,
                file,
                0,
            )
        }
        .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;

        Ok(Self {
            ptr,
            len: len.get(),
        })
    }

    fn as_bytes(&self) -> &[u8] {
        unsafe { std::slice::from_raw_parts(self.ptr.as_ptr() as *const u8, self.len) }
    }
}

impl Drop for MmapFile {
    fn drop(&mut self) {
        let _ = unsafe { munmap(self.ptr, self.len) };
    }
}

fn parse_elf_native<P: AsRef<Path>>(path: &P, check_linking: bool) {
    let _ = File::open(path)
        .ok()
        .and_then(|mut file| ExecutableFile::parse(black_box(&mut file), check_linking).ok());
}

fn parse_elf_goblin<P: AsRef<Path>>(path: &P, strict: bool, header_only: bool) {
    let _ = File::open(path)
        .ok()
        .and_then(|file| MmapFile::map_readonly(file).ok())
        .and_then(|data| {
            let data = data.as_bytes();
            if header_only {
                Elf::parse_header(black_box(data)).map(drop).ok()
            } else {
                let opts = if strict {
                    ParseOptions::strict()
                } else {
                    ParseOptions::permissive()
                };
                Elf::parse_with_opts(black_box(data), &opts).map(drop).ok()
            }
        });
}

fn main() {
    let mut paths = Vec::new();
    'main: for dir in STDPATH {
        let reader = if let Ok(reader) = read_dir(dir) {
            reader
        } else {
            continue;
        };

        for result in reader {
            let entry = if let Ok(entry) = result {
                entry
            } else {
                continue;
            };
            if entry.file_type().map(|ft| !ft.is_file()).unwrap_or(true) {
                continue;
            }
            paths.push(entry.path());
            if paths.len() >= 1000 {
                break 'main;
            }
        }
    }

    let paths = std::sync::Arc::new(paths);
    println!("Loaded {} paths for benchmarking.", paths.len());

    benches!(
        inline:
        Bench::new("parse_elf_native check_linking=0")
            .with_samples(paths.len().try_into().unwrap())
            .with_timeout(Duration::from_secs(10))
            .run_seeded(paths.clone(), |paths| {
                for path in paths.iter() {
                    black_box(parse_elf_native(path, false));
                }
            }),
        Bench::new("parse_elf_native check_linking=1")
            .with_samples(paths.len().try_into().unwrap())
            .with_timeout(Duration::from_secs(10))
            .run_seeded(paths.clone(), |paths| {
                for path in paths.iter() {
                    black_box(parse_elf_native(path, true));
                }
            }),
        Bench::new("parse_elf_goblin header_only")
            .with_samples(paths.len().try_into().unwrap())
            .with_timeout(Duration::from_secs(10))
            .run_seeded(paths.clone(), |paths| {
                for path in paths.iter() {
                    black_box(parse_elf_goblin(path, false, true));
                }
            }),
        Bench::new("parse_elf_goblin strict")
            .with_samples(paths.len().try_into().unwrap())
            .with_timeout(Duration::from_secs(10))
            .run_seeded(paths.clone(), |paths| {
                for path in paths.iter() {
                    black_box(parse_elf_goblin(path, true, false));
                }
            }),
        Bench::new("parse_elf_goblin permissive")
            .with_samples(paths.len().try_into().unwrap())
            .with_timeout(Duration::from_secs(10))
            .run_seeded(paths.clone(), |paths| {
                for path in paths.iter() {
                    black_box(parse_elf_goblin(path, false, false));
                }
            }),
    );
}
